[PR] この広告は3ヶ月以上更新がないため表示されています。
ホームページを更新後24時間以内に表示されなくなります。
3Dゲームの当たり判定、衝突判定って2Dと違って難しくなってくる領域ですよね。
今回は Bullet (ブレットよ呼ぶよ、バレットと間違えないでね)の衝突判定について勉強します。
状況に応じて、最適な当たり判定方法を選択できる予備知識を身につけたい!
そんな気持ちで、ドキュメントとサンプルコードを読み進めてみました。
こちら、036話のイラストを描いた日と同じ日に描いておいたものです。(いわゆるストック)
手前にある手を大きく描こうという気持ちで挑戦したのだけど、やっぱり難しいですね。
はい、すみません本題に戻ります。
まずこちらのドキュメントを最後まで読んでみましょう。
Bullet User Manual and API documentation
ああ、APIリファレンスまで全部読む必要はないです。
全部読みました?だいたい概要は理解できたと思うのですが
具体的に運用するにはサンプルを読んでみないとわからないですよね。
ということで、今回以降の入門講座は Bullet の
衝突判定 → 基本形状(衝突形状) → 関節の使い方 → ソフトボディ → オマケ(パフォーマンステスト)
の順番でサンプルソースを読解していこうと思います。
では今回は、「衝突判定」についてですね。
ドキュメントにて紹介がある通り、 Demo の Collision Demo から理解の突破口をつかみ
Simplex Demo → Collision Interfacing Demo → Gjk Convex Cast / Sweep Demo → Raytracer Demo
Continuous Convex Collision → User Collision Algorithm の順番で詳細を見ていくことにしましょう。
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 31 32 33 34 35 36 37 | /* Bullet Continuous Collision Detection and Physics Library Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef COLLISION_DEMO_H #define COLLISION_DEMO_H #include "GlutDemoApplication.h" ///CollisionDemo shows the low-level direct access to GJK class CollisionDemo : public GlutDemoApplication { public : void initPhysics(); virtual void clientMoveAndDisplay(); virtual void displayCallback(); virtual void specialKeyboardUp( int key, int x, int y); virtual void specialKeyboard( int key, int x, int y); }; #endif //COLLISION_DEMO_H |
ヘッダファイルは読んでもあまり意味ないですね。
以下のソースをもくもくと読み進めます…
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 | /* Bullet Continuous Collision Detection and Physics Library Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /// /// Collision Demo shows a degenerate case, where the Simplex solver has to deal with near-affine dependent cases /// See the define CATCH_DEGENERATE_TETRAHEDRON in Bullet's btVoronoiSimplexSolver.cpp /// //#define CHECK_GENSHER_TRIANGLE_CASE 1 ///This low-level internal demo does intentionally NOT use the btBulletCollisionCommon.h header ///It needs internal access #include "GL_Simplex1to4.h" #include "LinearMath/btQuaternion.h" #include "LinearMath/btTransform.h" #include "BulletCollision/NarrowPhaseCollision/btVoronoiSimplexSolver.h" #include "BulletCollision/CollisionShapes/btBoxShape.h" #include "BulletCollision/NarrowPhaseCollision/btGjkPairDetector.h" #include "BulletCollision/NarrowPhaseCollision/btPointCollector.h" #include "BulletCollision/NarrowPhaseCollision/btVoronoiSimplexSolver.h" #include "BulletCollision/NarrowPhaseCollision/btConvexPenetrationDepthSolver.h" #include "BulletCollision/NarrowPhaseCollision/btGjkEpaPenetrationDepthSolver.h" #include "LinearMath/btTransformUtil.h" #include "CollisionDemo.h" #include "GL_ShapeDrawer.h" #include "GlutStuff.h" #include "LinearMath/btIDebugDraw.h" #include "../OpenGL/GLDebugDrawer.h" GLDebugDrawer debugDrawer; float yaw=0.f,pitch=0.f,roll=0.f; const int maxNumObjects = 4; const int numObjects = 2; GL_Simplex1to4 simplex; btPolyhedralConvexShape* shapePtr[maxNumObjects]; btTransform tr[numObjects]; int screenWidth = 640; int screenHeight = 480; void DrawRasterizerLine( float const * , float const *, int ) { } int main( int argc, char ** argv) { CollisionDemo* colDemo = new CollisionDemo(); #ifdef CHECK_GENSHER_TRIANGLE_CASE colDemo->setCameraDistance(8.f); #else colDemo->setCameraDistance(4.f); #endif // colDemo->initPhysics(); return glutmain(argc, argv,screenWidth,screenHeight, "Collision Demo" ,colDemo); } void CollisionDemo::initPhysics() { setTexturing( false ); setShadows( false ); //m_debugMode |= btIDebugDraw::DBG_DrawWireframe; #ifdef CHECK_GENSHER_TRIANGLE_CASE m_azi = 140.f; #else m_azi = 250.f; #endif m_ele = 25.f; m_azi = 0; m_ele = 0; m_cameraTargetPosition.setValue(8.12,0.39,0); tr[0].setIdentity(); tr[0].setOrigin(btVector3(10,0,0)); tr[1].setIdentity(); tr[1].setOrigin(btVector3(0,0,0)); #ifdef CHECK_GENSHER_TRIANGLE_CASE tr[0].setIdentity(); tr[1].setIdentity(); #endif //CHECK_GENSHER_TRIANGLE_CASE btVector3 boxHalfExtentsA(1,1,1); //1.0000004768371582f,1.0000004768371582f,1.0000001192092896f); btVector3 boxHalfExtentsB(4,4,4); //3.2836332321166992f,3.2836332321166992f,3.2836320400238037f); #ifndef CHECK_GENSHER_TRIANGLE_CASE btBoxShape* boxA = new btBoxShape(boxHalfExtentsA); btBoxShape* boxB = new btBoxShape(boxHalfExtentsB); #endif #ifdef CHECK_GENSHER_TRIANGLE_CASE shapePtr[0] = trishapeA; shapePtr[1] = trishapeB; #else shapePtr[0] = boxA; shapePtr[1] = boxB; #endif } void CollisionDemo::clientMoveAndDisplay() { displayCallback(); } static btVoronoiSimplexSolver sGjkSimplexSolver; btSimplexSolverInterface& gGjkSimplexSolver = sGjkSimplexSolver; static btScalar gContactBreakingThreshold=.02f; int myiter = 1; int mystate = 2; int checkPerturbation = 1; int numPerturbationIterations = 20; void CollisionDemo::displayCallback( void ) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDisable(GL_LIGHTING); btVoronoiSimplexSolver sGjkSimplexSolver; btGjkEpaPenetrationDepthSolver epaSolver; btPointCollector gjkOutput; btVector3 worldBoundsMin(-1000,-1000,-1000); btVector3 worldBoundsMax(1000,1000,1000); { btGjkPairDetector convexConvex(shapePtr[0],shapePtr[1],&sGjkSimplexSolver,&epaSolver); btGjkPairDetector::ClosestPointInput input; input.m_transformA = tr[0]; input.m_transformB = tr[1]; convexConvex.getClosestPoints(input, gjkOutput, 0); } btScalar m[16]; int i; //m_ele = 21.2; //m_azi = -56.6; for (i=0;i<numObjects;i++) { tr[i].getOpenGLMatrix( m ); //m_shapeDrawer->drawOpenGL(m,shapePtr[i],btVector3(119./255.,147./255.,60./255.),btIDebugDraw::DBG_FastWireframe,worldBoundsMin,worldBoundsMax); m_shapeDrawer->drawOpenGL(m,shapePtr[i],btVector3(0.6,0.6,0.6),btIDebugDraw::DBG_FastWireframe,worldBoundsMin,worldBoundsMax); } if (gjkOutput.m_hasResult) { printf ( "original distance: %10.4f\n" , gjkOutput.m_distance); btVector3 endPt = gjkOutput.m_pointInWorld + gjkOutput.m_normalOnBInWorld*gjkOutput.m_distance; debugDrawer.drawLine(gjkOutput.m_pointInWorld,endPt,btVector3(0,0,0)); debugDrawer.drawSphere(gjkOutput.m_pointInWorld,0.05,btVector3(0,0,0)); debugDrawer.drawSphere(endPt,0.05,btVector3(0,0,0)); bool perturbeA = false ; //true; const btScalar angleLimit = 0.125f * SIMD_PI; btScalar perturbeAngle; btScalar radiusA = shapePtr[0]->getAngularMotionDisc(); btScalar radiusB = shapePtr[1]->getAngularMotionDisc(); if (radiusA < radiusB) { perturbeAngle = gContactBreakingThreshold /radiusA; perturbeA = true ; } else { perturbeAngle = gContactBreakingThreshold / radiusB; perturbeA = false ; } if ( perturbeAngle > angleLimit ) perturbeAngle = angleLimit; perturbeAngle*=5; btVector3 v0,v1; btPlaneSpace1(gjkOutput.m_normalOnBInWorld,v0,v1); glLineWidth(5); int i; i=0; if (myiter>=numPerturbationIterations) myiter=0; if (mystate<2) { i= myiter; } for ( ;i<numPerturbationIterations;i++) { btGjkPairDetector::ClosestPointInput input; input.m_transformA = tr[0]; input.m_transformB = tr[1]; sGjkSimplexSolver.reset(); btQuaternion perturbeRot(v0,perturbeAngle); btScalar iterationAngle = i*(SIMD_2_PI/btScalar(numPerturbationIterations)); btQuaternion rotq(gjkOutput.m_normalOnBInWorld,iterationAngle); if (perturbeA) { input.m_transformA.setBasis( btMatrix3x3(rotq*perturbeRot*rotq.inverse())*tr[0].getBasis()); } else { input.m_transformB.setBasis( btMatrix3x3(rotq.inverse()*perturbeRot*rotq)*tr[1].getBasis()); } debugDrawer.drawTransform(input.m_transformA,1.0); btGjkPairDetector convexConvex(shapePtr[0],shapePtr[1],&sGjkSimplexSolver,&epaSolver); input.m_maximumDistanceSquared = BT_LARGE_FLOAT; gjkOutput.m_distance = BT_LARGE_FLOAT; convexConvex.getClosestPoints(input, gjkOutput, 0); if (mystate!=2 || i==myiter) { btScalar m[16]; input.m_transformA.getOpenGLMatrix( m ); //m_shapeDrawer->drawOpenGL(m,shapePtr[0],btVector3(108./255.,131./255.,158./255),btIDebugDraw::DBG_FastWireframe,worldBoundsMin,worldBoundsMax); m_shapeDrawer->drawOpenGL(m,shapePtr[0],btVector3(0.3,0.3,1),btIDebugDraw::DBG_FastWireframe,worldBoundsMin,worldBoundsMax); } if (1) //gjkOutput.m_hasResult) { printf ( "perturbed distance: %10.4f\n" , gjkOutput.m_distance); btVector3 startPt,endPt; btScalar depth = 0; if (perturbeA) { btVector3 endPtOrg = gjkOutput.m_pointInWorld + gjkOutput.m_normalOnBInWorld*gjkOutput.m_distance; endPt = (tr[0]*input.m_transformA.inverse())(endPtOrg); depth = (endPt - gjkOutput.m_pointInWorld).dot(gjkOutput.m_normalOnBInWorld); startPt = endPt-gjkOutput.m_normalOnBInWorld*depth; } else { endPt = gjkOutput.m_pointInWorld + gjkOutput.m_normalOnBInWorld*gjkOutput.m_distance; startPt = (tr[1]*input.m_transformB.inverse())(gjkOutput.m_pointInWorld); depth = (endPt - startPt).dot(gjkOutput.m_normalOnBInWorld); } printf ( "corrected distance: %10.4f\n" , depth); debugDrawer.drawLine(startPt,endPt,btVector3(1,0,0)); debugDrawer.drawSphere(startPt,0.05,btVector3(0,1,0)); debugDrawer.drawSphere(endPt,0.05,btVector3(0,0,1)); } if (mystate<2) break ; if (mystate==2 && i>myiter) break ; } } static int looper = 0; if (looper++>10) { looper =0; checkPerturbation++; if (checkPerturbation>numPerturbationIterations) checkPerturbation=0; } GL_ShapeDrawer::drawCoordSystem(); if (mystate==1 || mystate==2) { static int count = 10; count--; if (count<0) { count=10; myiter++; } } btQuaternion orn; orn.setEuler(yaw,pitch,roll); //let it rotate //tr[0].setRotation(orn); pitch += 0.005f; yaw += 0.01f; glFlush(); glutSwapBuffers(); } void CollisionDemo::specialKeyboard( int key, int x, int y) { switch (key) { case GLUT_KEY_DOWN: case GLUT_KEY_UP: { break ; } default : DemoApplication::specialKeyboard(key,x,y); break ; } } void CollisionDemo::specialKeyboardUp( int key, int x, int y) { switch (key) { case GLUT_KEY_UP : { myiter++; break ; } case GLUT_KEY_DOWN: { mystate++; if (mystate>1) myiter=0; if (mystate>=4) mystate = 0; break ; } default : DemoApplication::specialKeyboardUp(key,x,y); break ; } } |
実行結果は次のようになります。
ふむふむ、では解説をしていきたいと思います。
左側の小さいのが boxA , 右側の大きいのが boxB です。
initPhysics() 関数内で boxA を向かって左へ10[m]移動、boxBは原点配置。
大きさはそれぞれ一辺 2[m] と 8[m]としています。
あまりやっちゃいけないことだけど、グローバル変数の shapePtr 配列に
boxA, boxBの順でポインタを渡しています。(解放し忘れて、メモリリークを引き起こしそうな書き方だな…)
なんか関数の合間にグローバル変数として次の記述があるけど、これも普通やってはいけません。
可読性を下げますから…せめて、コメントほしいんだけど…
static btVoronoiSimplexSolver sGjkSimplexSolver;
btSimplexSolverInterface& gGjkSimplexSolver = sGjkSimplexSolver;
static btScalar gContactBreakingThreshold=.02f;
int myiter = 1;
int mystate = 2;
int checkPerturbation = 1;
int numPerturbationIterations = 20;
ちなみに "Perturbation" とは、物理用語の 摂動 《惑星などがその引力によって他の惑星などの運動を乱すこと》.を意味するらしい。
ひとまず、衝突物体の形状と姿勢が決まったので次のコードで描画しています。
描画コードはみなさんそれぞれ独自のものだから、このコードは正直何しているかわかればよいです。
1 2 3 4 5 6 | for (i=0;i<numObjects;i++) { tr[i].getOpenGLMatrix( m ); //m_shapeDrawer->drawOpenGL(m,shapePtr[i],btVector3(119./255.,147./255.,60./255.),btIDebugDraw::DBG_FastWireframe,worldBoundsMin,worldBoundsMax); m_shapeDrawer->drawOpenGL(m,shapePtr[i],btVector3(0.6,0.6,0.6),btIDebugDraw::DBG_FastWireframe,worldBoundsMin,worldBoundsMax); } |
今回の衝突判定の肝となる部分が以下のコード
物体の衝突を確認するための検出器の作成と、その使い方のコード部分です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | btVoronoiSimplexSolver sGjkSimplexSolver; btGjkEpaPenetrationDepthSolver epaSolver; btPointCollector gjkOutput; btVector3 worldBoundsMin(-1000,-1000,-1000); btVector3 worldBoundsMax(1000,1000,1000); { btGjkPairDetector convexConvex(shapePtr[0],shapePtr[1],&sGjkSimplexSolver,&epaSolver); btGjkPairDetector::ClosestPointInput input; input.m_transformA = tr[0]; input.m_transformB = tr[1]; convexConvex.getClosestPoints(input, gjkOutput, 0); } |
衝突形状を2つ登録して、使うときはそれぞれの姿勢行列を渡しているのがうかがえます。
getClosestPoints 関数で両者にとっての最近傍点を求めることができるようです。
なんとも、わかりやすいですね。
では、一体どんな情報が手に入ったのか具体的に見ていきましょう。
で確認しているコードが次の箇所でした。
1 2 3 4 5 6 7 | printf ( "original distance: %10.4f\n" , gjkOutput.m_distance); btVector3 endPt = gjkOutput.m_pointInWorld + gjkOutput.m_normalOnBInWorld*gjkOutput.m_distance; debugDrawer.drawLine(gjkOutput.m_pointInWorld,endPt,btVector3(0,0,0)); debugDrawer.drawSphere(gjkOutput.m_pointInWorld,0.05,btVector3(1,0,0)); debugDrawer.drawSphere(endPt,0.05,btVector3(0,0,1)); |
m_distance には 5[m] の値が入っていました。
つまり、ボックスとボックスの間の距離 10 - (1 + 4) m が結果として与えられているわけ。
また、m_pointInWorld に入っている座標は boxB の面上の位置でした。(感覚とは逆のような気が…)
そして、m_normalOnBInWorld はその名前の通り
boxB の位置から、boxA への最近傍点へ伸ばした方向ベクトルの単位ベクトルを意味しています。
これで2つの形状の衝突判定方法が具体的になりました。
btGjkPairDetector に 2つの btConvexShape ポインタを渡して初期化し、任意の Solver を指定しておきます。
あとは ClosestPointInput 構造体のメンバにそれぞれの物体の姿勢行列を渡して、getClosestPoints 関数を呼ぶだけ!
gjkOutput には上記で確認した情報が入ります。
当たっているかどうかの判定は、 m_distance の値を見れば一目瞭然ですね。
試しに、boxA を向かって左へ 5[m] の位置に配置した場合は m_distance は 0[m] になりました。
boxA を向かって左へ 4[m] の位置に配置した場合は m_distance は -1[m] になりました。
まず、これだけで衝突判定、お互いの衝突までの距離を求めることができます。
続きのコードは初見の人には、正直わかりづらいです。
やっていることは boxA の姿勢を計算して描画し、そのときの最近傍点のペアを表示するというもの。
あまりのわかりづらさにイライラしたので
アニメーションしながら、最近傍点ペアを結ぶ線分を随時描画するものに書き換えてみました。
私と同じで残りのコードの理解が嫌な方は次の方法を試してみてはいかがでしょうか?
次の関数を入れ替えて実行します。(こっちの方がわかりやすいと思うんですよ!)
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | void CollisionDemo::displayCallback( void ) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDisable(GL_LIGHTING); btVoronoiSimplexSolver sGjkSimplexSolver; btGjkEpaPenetrationDepthSolver epaSolver; btPointCollector gjkOutput; btVector3 worldBoundsMin(-1000,-1000,-1000); btVector3 worldBoundsMax(1000,1000,1000); // draw boxB { btScalar m[16]; tr[1].getOpenGLMatrix( m ); m_shapeDrawer->drawOpenGL(m,shapePtr[1],btVector3(0.6,0.6,0.6),btIDebugDraw::DBG_FastWireframe,worldBoundsMin,worldBoundsMax); } { bool perturbeA = false ; //true; const btScalar angleLimit = 0.125f * SIMD_PI; btScalar perturbeAngle; btScalar radiusA = shapePtr[0]->getAngularMotionDisc(); btScalar radiusB = shapePtr[1]->getAngularMotionDisc(); if (radiusA < radiusB) { perturbeAngle = gContactBreakingThreshold /radiusA; perturbeA = true ; } else { perturbeAngle = gContactBreakingThreshold / radiusB; perturbeA = false ; } if ( perturbeAngle > angleLimit ) perturbeAngle = angleLimit; perturbeAngle*=5; btVector3 v0,v1; btPlaneSpace1(gjkOutput.m_normalOnBInWorld,v0,v1); glLineWidth(1); // boxAの回転角度を決める要素の更新 static float g_iterator = 0; g_iterator += 0.1; if (g_iterator>=numPerturbationIterations) { g_iterator=0; } // draw closest point pair { btGjkPairDetector::ClosestPointInput input; input.m_transformA = tr[0]; input.m_transformB = tr[1]; sGjkSimplexSolver.reset(); // boxA の姿勢を計算 btQuaternion perturbeRot(v0,perturbeAngle); btScalar iterationAngle = g_iterator*(SIMD_2_PI/btScalar(numPerturbationIterations)); btQuaternion rotq(gjkOutput.m_normalOnBInWorld,iterationAngle); if (perturbeA) { input.m_transformA.setBasis( btMatrix3x3(rotq*perturbeRot*rotq.inverse())*tr[0].getBasis()); } else { input.m_transformB.setBasis( btMatrix3x3(rotq.inverse()*perturbeRot*rotq)*tr[1].getBasis()); } // boxAの座標軸の描画 debugDrawer.drawTransform(input.m_transformA,1.0); btGjkPairDetector convexConvex(shapePtr[0],shapePtr[1],&sGjkSimplexSolver,&epaSolver); input.m_maximumDistanceSquared = BT_LARGE_FLOAT; gjkOutput.m_distance = BT_LARGE_FLOAT; convexConvex.getClosestPoints(input, gjkOutput, 0); // draw boxA { btScalar m[16]; input.m_transformA.getOpenGLMatrix( m ); m_shapeDrawer->drawOpenGL(m,shapePtr[0],btVector3(0.3,0.3,1),btIDebugDraw::DBG_FastWireframe,worldBoundsMin,worldBoundsMax); } // 最近傍点のペアの描画 if (gjkOutput.m_hasResult) { printf ( "perturbed distance: %10.4f\n" , gjkOutput.m_distance); btVector3 startPt,endPt; startPt = gjkOutput.m_pointInWorld; endPt = gjkOutput.m_pointInWorld + gjkOutput.m_normalOnBInWorld*gjkOutput.m_distance; debugDrawer.drawLine(startPt,endPt,btVector3(1,0,0)); // 赤ライン debugDrawer.drawSphere(startPt,0.05,btVector3(0,1,0)); // boxB上 debugDrawer.drawSphere(endPt,0.05,btVector3(0,0,1)); // boxA上 } } } static int looper = 0; if (looper++>10) { looper =0; checkPerturbation++; if (checkPerturbation>numPerturbationIterations) checkPerturbation=0; } GL_ShapeDrawer::drawCoordSystem(); btQuaternion orn; orn.setEuler(yaw,pitch,roll); pitch += 0.005f; yaw += 0.01f; glFlush(); glutSwapBuffers(); } |
実行結果はこんな感じになります。
いかがでしたでしょうか?
Bullet の衝突判定の突破口をつかめましたでしょうか?
ここまでで出てきたのは、衝突形状のペアを設定して、それぞれの位置姿勢から
お互いのの最近傍点を求めるというものでした。
(キーワードは btGjkPairDetector です。)
こちらのデモは実行すると、四面体の表面から原点までの距離で最短のラインを描画するというものです。
そもそも Simplex って何なんでしょう。
ドキュメントの記述を読むと、どうも 1 から 4 点で作成されるオブジェクトのことを指していますね。
以下はドキュメントの記述そのまま。
This is a very low level demo testing the inner workings of the GJK sub distance algorithm. This calculates the distance between a simplex and the origin, which is drawn with a red line. A simplex contains 1 up to 4 points, the demo shows the 4 point case, a tetrahedron. The Voronoi simplex solver is used, as described by Christer Ericson in his collision detection book.
おわかり?
用途として、原点からの最近傍点を求めるのに使える感じでしょうか?
比較的高速に処理結果が得られるようですが
対象が四面体に限られると用途がすぐに思いつかないです。
ひとまず、こんな方法もあるということを覚えておこうと思います。
衝突判定方法は、 btGjkPairDetector を使う方法だけではありません。
ほかにどんなものがあるのか、このデモで確認してみましょう。
かなり役立つ情報が満載なデモです。
ではデモのソースコードを読んでみてください。
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 31 32 33 34 35 36 37 38 39 40 41 | /* Bullet Continuous Collision Detection and Physics Library Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef COLLISION_INTERFACE_DEMO_H #define COLLISION_INTERFACE_DEMO_H #ifdef _WINDOWS #include "Win32DemoApplication.h" #define PlatformDemoApplication Win32DemoApplication #else #include "GlutDemoApplication.h" #define PlatformDemoApplication GlutDemoApplication #endif ///CollisionInterfaceDemo shows how to use the collision detection without dynamics (btCollisionWorld/CollisionObject) class CollisionInterfaceDemo : public PlatformDemoApplication { public : void initPhysics(); virtual void clientMoveAndDisplay(); virtual void displayCallback(); virtual void clientResetScene(); }; #endif //COLLISION_INTERFACE_DEMO_H |
相変わらずヘッダファイルに情報はありませんね。
引き続き実装を見てみましょう。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | /* Bullet Continuous Collision Detection and Physics Library Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /// /// CollisionInterfaceDemo shows high level usage of the Collision Detection. /// #define TEST_NOT_ADDING_OBJECTS_TO_WORLD #include "GL_Simplex1to4.h" //include common Bullet Collision Detection headerfiles #include "btBulletCollisionCommon.h" #include "LinearMath/btIDebugDraw.h" #include "GL_ShapeDrawer.h" #include "CollisionInterfaceDemo.h" #include "GlutStuff.h" #include "GLDebugDrawer.h" btScalar yaw=0.f,pitch=0.f,roll=0.f; const int maxNumObjects = 4; const int numObjects = 2; GL_Simplex1to4 simplex; btCollisionObject objects[maxNumObjects]; btCollisionWorld* collisionWorld = 0; GLDebugDrawer debugDrawer; void CollisionInterfaceDemo::initPhysics() { m_debugMode |= btIDebugDraw::DBG_DrawWireframe; btMatrix3x3 basisA; basisA.setIdentity(); btMatrix3x3 basisB; basisB.setIdentity(); objects[0].getWorldTransform().setBasis(basisA); objects[1].getWorldTransform().setBasis(basisB); btBoxShape* boxA = new btBoxShape(btVector3(1,1,1)); boxA->setMargin(0.f); btBoxShape* boxB = new btBoxShape(btVector3(0.5,0.5,0.5)); boxB->setMargin(0.f); //ConvexHullShape hullA(points0,3); //hullA.setLocalScaling(btVector3(3,3,3)); //ConvexHullShape hullB(points1,4); //hullB.setLocalScaling(btVector3(4,4,4)); objects[0].setCollisionShape(boxA); //&hullA; objects[1].setCollisionShape(boxB); //&hullB; btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration(); btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration); btVector3 worldAabbMin(-1000,-1000,-1000); btVector3 worldAabbMax(1000,1000,1000); btAxisSweep3* broadphase = new btAxisSweep3(worldAabbMin,worldAabbMax); //SimpleBroadphase is a brute force alternative, performing N^2 aabb overlap tests //SimpleBroadphase* broadphase = new btSimpleBroadphase; collisionWorld = new btCollisionWorld(dispatcher,broadphase,collisionConfiguration); collisionWorld->setDebugDrawer(&debugDrawer); #ifdef TEST_NOT_ADDING_OBJECTS_TO_WORLD // collisionWorld->addCollisionObject(&objects[0]); collisionWorld->addCollisionObject(&objects[1]); #endif //TEST_NOT_ADDING_OBJECTS_TO_WORLD } //to be implemented by the demo void CollisionInterfaceDemo::clientMoveAndDisplay() { displayCallback(); } static btVoronoiSimplexSolver sGjkSimplexSolver; btSimplexSolverInterface& gGjkSimplexSolver = sGjkSimplexSolver; struct btDrawingResult : public btCollisionWorld::ContactResultCallback { virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObject* colObj0, int partId0, int index0, const btCollisionObject* colObj1, int partId1, int index1) { glBegin(GL_LINES); glColor3f(1, 0, 0); btVector3 ptA = cp.getPositionWorldOnA(); btVector3 ptB = cp.getPositionWorldOnB(); glVertex3d(ptA.x(),ptA.y(),ptA.z()); glVertex3d(ptB.x(),ptB.y(),ptB.z()); glEnd(); return 0; } }; void CollisionInterfaceDemo::displayCallback( void ) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDisable(GL_LIGHTING); btScalar m[16]; btVector3 worldBoundsMin,worldBoundsMax; collisionWorld->getBroadphase()->getBroadphaseAabb(worldBoundsMin,worldBoundsMax); int i; for (i=0;i<numObjects;i++) { objects[i].getWorldTransform().getOpenGLMatrix( m ); m_shapeDrawer->drawOpenGL(m,objects[i].getCollisionShape(),btVector3(1,1,1),getDebugMode(),worldBoundsMin,worldBoundsMax); } collisionWorld->getDispatchInfo().m_debugDraw = &debugDrawer; if (collisionWorld) collisionWorld->performDiscreteCollisionDetection(); #ifndef TEST_NOT_ADDING_OBJECTS_TO_WORLD collisionWorld->debugDrawWorld(); ///one way to draw all the contact points is iterating over contact manifolds in the dispatcher: int numManifolds = collisionWorld->getDispatcher()->getNumManifolds(); for (i=0;i<numManifolds;i++) { btPersistentManifold* contactManifold = collisionWorld->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); glBegin(GL_LINES); glColor3f(0, 0, 0); btVector3 ptA = pt.getPositionWorldOnA(); btVector3 ptB = pt.getPositionWorldOnB(); glVertex3d(ptA.x(),ptA.y(),ptA.z()); glVertex3d(ptB.x(),ptB.y(),ptB.z()); glEnd(); } //you can un-comment out this line, and then all points are removed //contactManifold->clearManifold(); } #else glDisable(GL_TEXTURE_2D); for (i=0;i<numObjects;i++) { collisionWorld->debugDrawObject(objects[i].getWorldTransform(),objects[i].getCollisionShape(), btVector3(1,1,0)); } btDrawingResult renderCallback; //collisionWorld->contactPairTest(&objects[0],&objects[1], renderCallback); collisionWorld->contactTest(&objects[0],renderCallback); #if 0 //another way is to directly query the dispatcher for both objects. The objects don't need to be inserted into the world btCollisionAlgorithm* algo = collisionWorld->getDispatcher()->findAlgorithm(&objects[0],&objects[1]); btManifoldResult contactPointResult(&objects[0],&objects[1]); algo->processCollision(&objects[0],&objects[1],collisionWorld->getDispatchInfo(),&contactPointResult); btManifoldArray manifoldArray; algo->getAllContactManifolds(manifoldArray); int numManifolds = manifoldArray.size(); for (i=0;i<numManifolds;i++) { btPersistentManifold* contactManifold = manifoldArray[i]; btCollisionObject* obA = static_cast <btCollisionObject*>(contactManifold->getBody0()); // btCollisionObject* obB = static_cast<btCollisionObject*>(contactManifold->getBody1()); glDisable(GL_DEPTH_TEST); int numContacts = contactManifold->getNumContacts(); bool swap = obA == &objects[0]; for ( int j=0;j<numContacts;j++) { btManifoldPoint& pt = contactManifold->getContactPoint(j); glBegin(GL_LINES); glColor3f(0, 0, 0); btVector3 ptA = swap ?pt.getPositionWorldOnA():pt.getPositionWorldOnB(); btVector3 ptB = swap ? pt.getPositionWorldOnB():pt.getPositionWorldOnA(); glVertex3d(ptA.x(),ptA.y(),ptA.z()); glVertex3d(ptB.x(),ptB.y(),ptB.z()); glEnd(); } //you can un-comment out this line, and then all points are removed //contactManifold->clearManifold(); } #endif #endif //GL_ShapeDrawer::drawCoordSystem(); btQuaternion qA = objects[0].getWorldTransform().getRotation(); btQuaternion qB = objects[1].getWorldTransform().getRotation(); if (!m_idle) { btScalar timeInSeconds = getDeltaTimeMicroseconds()/1000.f; btQuaternion orn; objects[0].getWorldTransform().getBasis().getEulerYPR(yaw,pitch,roll); pitch += 0.00005f*timeInSeconds; yaw += 0.0001f*timeInSeconds; objects[0].getWorldTransform().getBasis().setEulerYPR(yaw,pitch,roll); orn.setEuler(yaw,pitch,roll); objects[1].getWorldTransform().setOrigin(objects[1].getWorldTransform().getOrigin()+btVector3(0,-0.00001*timeInSeconds,0)); //objects[0].getWorldTransform().setRotation(orn); } glFlush(); swapBuffers(); } void CollisionInterfaceDemo::clientResetScene() { objects[0].getWorldTransform().setOrigin(btVector3(0.0f,3.f,0.f)); btQuaternion rotA(0.739f,-0.204f,0.587f,0.257f); rotA.normalize(); objects[0].getWorldTransform().setRotation(rotA); objects[1].getWorldTransform().setOrigin(btVector3(0.0f,4.5f,0.f)); } |
実行結果がこちら↓
今回新しくコールバック関数というものが登場していることを確認しましたでしょうか?
さっそく解説していきたいと思います。
お決まりの initPhysics() 関数で boxA (一辺 2 [m]), boxB (一辺 1 [m])のボックスを衝突形状として作成して
これまたグローバル変数の btCollisionObject 配列にそれぞれ順番に([0]と[1]に)設定しています。
(今回はシェイプのポインタを渡さず setCollisonShape 関数で衝突オブジェクトにシェイプを設定していますね。)
あとは btCollisionWorld を btDefaultCollisionConfiguration, btCollisionDispatcher, btAxisSweep3 から作成しています。
だんだん World の作り方に慣れてきたころでしょうか。
今回の肝となる部分がこちら↓ (ソースにはハイライトを入れておきました。)
struct btDrawingResult : public btCollisionWorld::ContactResultCallback
日本語だと接触の結果通知構造体?(いやいや、英語のままでいいですね。)
これを継承している btDrawingResult 構造体のメンバ関数 addSingleResult をオーバーライドして
第一引数に入っている結果より、boxA, boxBの接触点の位置を取り出し、これを結ぶ線分を描画しているんです。
(どういう訳かAPIリファレンスにこの構造体の情報が無いんですよね…比較的新しい情報なんでしょうか?)
どこにも説明が無いので、想像になってしまいますが btManifoldPoint には、両接触点の位置と接触深度が入っています。
第2引数以降は名前から察するに、boxA, boxB のオブジェクト情報と
btCollisionWorld におけるそれぞれのインデックスでしょうね。
この構造体の使い方は簡単、次のように contactTest の引数に参照を渡すだけです。
1 2 3 4 | btDrawingResult renderCallback; //collisionWorld->contactPairTest(&objects[0],&objects[1], renderCallback); collisionWorld->contactTest(&objects[0],renderCallback); |
指定したオブジェクトが「何か」(もちろん衝突物体ですが)に接触していれば、コールバック関数が呼ばれる仕組みです。
呼ばれる回数は接触点の数だけ呼ばれます。
コメントアウトしているコードに注目してください
これは、2つのオブジェクトを指定してその接触結果をコールバックする場合の方法です。
つまり btCollisionWorld::contactPairTest を使えばOKということ。
そのほかのコードを読んでみましたが、オブジェクトの移動と回転の制御をしているだけなので
特に解説する必要はない感じです。
さて、ひときわ興味深いのは #if 0 で論理的に到達できないコードの部分です。
こちらを通るようにすると、結局のところ contactTest と同じ結果が得られます。
使い方の利点は、コメントにある通り btCollisionWorld に登録していなくても結果が得られるという点です。
その他の利点は接触点ごとにループ処理を書けるので、細かい対応はこちらの方が簡単に行えそうです。
気になる使い方はこんな感じ↓
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 | //another way is to directly query the dispatcher for both objects. The objects don't need to be inserted into the world // アルゴリズムの取得 btCollisionAlgorithm* algo = collisionWorld->getDispatcher()->findAlgorithm(&objects[0],&objects[1]); // 2つのオブジェクトの衝突結果の取得 btManifoldResult contactPointResult(&objects[0],&objects[1]); algo->processCollision(&objects[0],&objects[1],collisionWorld->getDispatchInfo(),&contactPointResult); // すべての衝突結果を得たい場合は、こうして多様体配列を取得します。 btManifoldArray manifoldArray; algo->getAllContactManifolds(manifoldArray); // いまさらですが、manifold とは多様体のこと。 int numManifolds = manifoldArray.size(); for (i=0;i<numManifolds;i++) { btPersistentManifold* contactManifold = manifoldArray[i]; // 接触点ペアの数 int numContacts = contactManifold->getNumContacts(); for ( int j=0;j<numContacts;j++) { // 接触点ペア情報の取得(それぞれのオブジェクト上の位置と距離など) btManifoldPoint& pt = contactManifold->getContactPoint(j); } } |
今回の結果を得たいだけならば、 numManifolds でループせずとも
btPersistentManifold* contactManifold = contactPointResult.getPersistentManifold();
で得た2つのオブジェクト間の接触結果で十分でした。
この辺、もう少しコメント書いておいてほしいですよね…まったく、わかりづらいったらありゃしない!
とにかく!ここからわかるのは world の dispatcher から オブジェクト間のアルゴリズムさえ取得できれば
アルゴリズムの processCollision 関数で2つのオブジェクト間の衝突判定結果が得られるということ。
また、 getAllContactManifolds を使えばアルゴリズムが処理するすべての接触点処理ができるというわけです。
便利便利!
ちなみにエントリポイントがコチラ↓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include "CollisionInterfaceDemo.h" #include "GlutStuff.h" #include "btBulletDynamicsCommon.h" int screenWidth = 640; int screenHeight = 480; int main( int argc, char ** argv) { CollisionInterfaceDemo* collisionInterfaceDemo = new CollisionInterfaceDemo(); collisionInterfaceDemo->initPhysics(); collisionInterfaceDemo->clientResetScene(); return glutmain(argc, argv,screenWidth,screenHeight, "Collision Interface Demo" ,collisionInterfaceDemo); } |
いかがでしたでしょうか?
今回の CollisionInterface デモで知ることになった衝突結果を取得する方法を以下にまとめます。
btCollisionWorld::ContactResultCallback を world に登録して
contactTest または contactPairTest でコールバック関数 addSingleResult が呼ばれるというもの。
オーバーライドしておけば、任意の処理がそこで行えるようになります。
(当たった時だけ反応する系のアクションに向いているコーディングですね。)
もう一つは
world の dispatcher から オブジェクト間のアルゴリズムを取得し
processCollision で2つのオブジェクト間の衝突結果を取得する方法です。
(getAllContactManifolds でアルゴリズムが処理するすべての接触点判定の取得もできます。)
はい、ということで
この調子でもっといろいろなケースの衝突判定結果の取得方法を見ていきましょう。
このデモ、ドキュメントを読む限りでは、事前に衝突を検知して、衝突までの時間から
壁に沿って移動するキャラクターなどに使うと便利…と書かれています。
具体的にどうやるのか知りたいので、デモのコードを解読していきましょう。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 | /* Bullet Continuous Collision Detection and Physics Library Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include "btBulletDynamicsCommon.h" #include "LinearMath/btIDebugDraw.h" #include "BulletCollision/CollisionShapes/btBoxShape.h" #include "GLDebugDrawer.h" #include "ConcaveConvexcastDemo.h" #include "GL_ShapeDrawer.h" #include "GlutStuff.h" #define NUM_DYNAMIC_BOXES_X 30 #define NUM_DYNAMIC_BOXES_Y 30 static btVector3* gVertices=0; static int * gIndices=0; static btBvhTriangleMeshShape* trimeshShape =0; static btRigidBody* staticBody = 0; static float waveheight = 5.f; const float TRIANGLE_SIZE=8.f; /* Scrolls back and forth over terrain */ #define NUMRAYS_IN_BAR 100 class btConvexcastBatch { public : btVector3 source[NUMRAYS_IN_BAR]; btVector3 dest[NUMRAYS_IN_BAR]; btVector3 direction[NUMRAYS_IN_BAR]; btVector3 hit_com[NUMRAYS_IN_BAR]; btVector3 hit_surface[NUMRAYS_IN_BAR]; btScalar hit_fraction[NUMRAYS_IN_BAR]; btVector3 normal[NUMRAYS_IN_BAR]; int frame_counter; int ms; int sum_ms; int sum_ms_samples; int min_ms; int max_ms; #ifdef USE_BT_CLOCK btClock frame_timer; #endif //USE_BT_CLOCK btScalar dx; btScalar min_x; btScalar max_x; btScalar min_y; btScalar max_y; btScalar sign; btVector3 boxShapeHalfExtents; btBoxShape boxShape; btConvexcastBatch () : boxShape(btVector3(0.0, 0.0, 0.0)) { ms = 0; max_ms = 0; min_ms = 9999.0; sum_ms_samples = 0; sum_ms = 0; } btConvexcastBatch ( bool unused, btScalar ray_length, btScalar min_z, btScalar max_z, btScalar min_y , btScalar max_y ) : boxShape(btVector3(0.0, 0.0, 0.0)) { boxShapeHalfExtents = btVector3(1.0, 1.0, 1.0); boxShape = btBoxShape(boxShapeHalfExtents); frame_counter = 0; ms = 0; max_ms = 0; min_ms = 9999.0; sum_ms_samples = 0; sum_ms = 0; dx = 10.0; min_x = -40; max_x = 20; this ->min_y = min_y; this ->max_y = max_y; sign = 1.0; btScalar dalpha = 2*SIMD_2_PI/NUMRAYS_IN_BAR; for ( int i = 0; i < NUMRAYS_IN_BAR; i++) { btScalar z = (max_z-min_z)/NUMRAYS_IN_BAR * i + min_z; source[i] = btVector3(min_x, max_y, z); dest[i] = btVector3(min_x + ray_length, min_y, z); normal[i] = btVector3(1.0, 0.0, 0.0); } } btConvexcastBatch (btScalar ray_length, btScalar z, btScalar min_y = -1000, btScalar max_y = 10) : boxShape(btVector3(0.0, 0.0, 0.0)) { boxShapeHalfExtents = btVector3(1.0, 1.0, 1.0); boxShape = btBoxShape(boxShapeHalfExtents); frame_counter = 0; ms = 0; max_ms = 0; min_ms = 9999.0; sum_ms_samples = 0; sum_ms = 0; dx = 10.0; min_x = -40; max_x = 20; this ->min_y = min_y; this ->max_y = max_y; sign = 1.0; btScalar dalpha = btScalar(2)*SIMD_2_PI/btScalar(NUMRAYS_IN_BAR); for ( int i = 0; i < NUMRAYS_IN_BAR; i++) { btScalar alpha = dalpha * btScalar(i); // rotate around by alpha degrees y btTransform tr(btQuaternion(btVector3(0.0, 1.0, 0.0), alpha)); direction[i] = btVector3(1.0, 0.0, 0.0); direction[i] = tr * direction[i]; source[i] = btVector3(min_x, max_y, z); dest[i] = source[i] + direction[i] * ray_length; dest[i][1] = min_y; normal[i] = btVector3(1.0, 0.0, 0.0); } } void move (btScalar dt) { if (dt > (1.0/60.0)) dt = 1.0/60.0; for ( int i = 0; i < NUMRAYS_IN_BAR; i++) { source[i][0] += dx * dt * sign; dest[i][0] += dx * dt * sign; } if (source[0][0] < min_x) sign = 1.0; else if (source[0][0] > max_x) sign = -1.0; } void cast (btCollisionWorld* cw) { #ifdef USE_BT_CLOCK frame_timer.reset (); #endif //USE_BT_CLOCK for ( int i = 0; i < NUMRAYS_IN_BAR; i++) { btCollisionWorld::ClosestConvexResultCallback cb(source[i], dest[i]); btQuaternion qFrom; btQuaternion qTo; qFrom.setRotation (btVector3(1.0, 0.0, 0.0), 0.0); qTo.setRotation (btVector3(1.0, 0.0, 0.0), 0.7); btTransform from(qFrom, source[i]); btTransform to(qTo, dest[i]); cw->convexSweepTest (&boxShape, from, to, cb); if (cb.hasHit ()) { hit_surface[i] = cb.m_hitPointWorld; hit_com[i].setInterpolate3(source[i], dest[i], cb.m_closestHitFraction); hit_fraction[i] = cb.m_closestHitFraction; normal[i] = cb.m_hitNormalWorld; normal[i].normalize (); } else { hit_com[i] = dest[i]; hit_surface[i] = dest[i]; hit_fraction[i] = 1.0f; normal[i] = btVector3(1.0, 0.0, 0.0); } } #ifdef USE_BT_CLOCK ms += frame_timer.getTimeMilliseconds (); #endif //USE_BT_CLOCK frame_counter++; if (frame_counter > 50) { min_ms = ms < min_ms ? ms : min_ms; max_ms = ms > max_ms ? ms : max_ms; sum_ms += ms; sum_ms_samples++; btScalar mean_ms = (btScalar)sum_ms/(btScalar)sum_ms_samples; printf ( "%d rays in %d ms %d %d %f\n" , NUMRAYS_IN_BAR * frame_counter, ms, min_ms, max_ms, mean_ms); ms = 0; frame_counter = 0; } } void drawCube ( const btTransform& T) { btScalar m[16]; T.getOpenGLMatrix (&m[0]); glPushMatrix (); #ifdef BT_USE_DOUBLE_PRECISION glMultMatrixd (&m[0]); glScaled (2.0 * boxShapeHalfExtents[0], 2.0 * boxShapeHalfExtents[1], 2.0 * boxShapeHalfExtents[2]); #else glMultMatrixf (&m[0]); glScalef (2.0 * boxShapeHalfExtents[0], 2.0 * boxShapeHalfExtents[1], 2.0 * boxShapeHalfExtents[2]); #endif //BT_USE_DOUBLE_PRECISION glutSolidCube (1.0); glPopMatrix (); } void draw () { glDisable (GL_LIGHTING); glColor3f (0.0, 1.0, 0.0); glBegin (GL_LINES); int i; for (i = 0; i < NUMRAYS_IN_BAR; i++) { glVertex3f (source[i][0], source[i][1], source[i][2]); glVertex3f (hit_com[i][0], hit_com[i][1], hit_com[i][2]); } glColor3f (1.0, 1.0, 1.0); glBegin (GL_LINES); btScalar normal_scale = 10.0; // easier to see if this is big for (i = 0; i < NUMRAYS_IN_BAR; i++) { glVertex3f (hit_surface[i][0], hit_surface[i][1], hit_surface[i][2]); glVertex3f (hit_surface[i][0] + normal_scale * normal[i][0], hit_surface[i][1] + normal_scale * normal[i][1], hit_surface[i][2] + normal_scale * normal[i][2]); } glEnd (); glColor3f (0.0, 1.0, 1.0); btQuaternion qFrom; btQuaternion qTo; qFrom.setRotation (btVector3(1.0, 0.0, 0.0), 0.0); qTo.setRotation (btVector3(1.0, 0.0, 0.0), 0.7); for ( i = 0; i < NUMRAYS_IN_BAR; i++) { btTransform from(qFrom, source[i]); btTransform to(qTo, dest[i]); btVector3 linVel, angVel; btTransformUtil::calculateVelocity (from, to, 1.0, linVel, angVel); btTransform T; btTransformUtil::integrateTransform (from, linVel, angVel, hit_fraction[i], T); drawCube (T); } glEnable (GL_LIGHTING); } }; static btConvexcastBatch convexcastBatch; const int NUM_VERTS_X = 30; const int NUM_VERTS_Y = 30; const int totalVerts = NUM_VERTS_X*NUM_VERTS_Y; void ConcaveConvexcastDemo::setVertexPositions( float waveheight, float offset) { int i; int j; for ( i=0;i<NUM_VERTS_X;i++) { for (j=0;j<NUM_VERTS_Y;j++) { gVertices[i+j*NUM_VERTS_X].setValue((i-NUM_VERTS_X*0.5f)*TRIANGLE_SIZE, //0.f, waveheight*sinf(( float )i+offset)*cosf(( float )j+offset), (j-NUM_VERTS_Y*0.5f)*TRIANGLE_SIZE); } } } void ConcaveConvexcastDemo::keyboardCallback(unsigned char key, int x, int y) { if (key == 'g' ) { m_animatedMesh = !m_animatedMesh; if (m_animatedMesh) { staticBody->setCollisionFlags( staticBody->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT); staticBody->setActivationState(DISABLE_DEACTIVATION); } else { staticBody->setCollisionFlags( staticBody->getCollisionFlags() & ~btCollisionObject::CF_KINEMATIC_OBJECT); staticBody->forceActivationState(ACTIVE_TAG); } } DemoApplication::keyboardCallback(key,x,y); } void ConcaveConvexcastDemo::initPhysics() { #define TRISIZE 10.f setCameraDistance(100.f); int vertStride = sizeof (btVector3); int indexStride = 3* sizeof ( int ); const int totalTriangles = 2*(NUM_VERTS_X-1)*(NUM_VERTS_Y-1); gVertices = new btVector3[totalVerts]; gIndices = new int [totalTriangles*3]; int i; setVertexPositions(waveheight,0.f); int index=0; for ( i=0;i<NUM_VERTS_X-1;i++) { for ( int j=0;j<NUM_VERTS_Y-1;j++) { gIndices[index++] = j*NUM_VERTS_X+i; gIndices[index++] = j*NUM_VERTS_X+i+1; gIndices[index++] = (j+1)*NUM_VERTS_X+i+1; gIndices[index++] = j*NUM_VERTS_X+i; gIndices[index++] = (j+1)*NUM_VERTS_X+i+1; gIndices[index++] = (j+1)*NUM_VERTS_X+i; } } m_indexVertexArrays = new btTriangleIndexVertexArray(totalTriangles, gIndices, indexStride, totalVerts,(btScalar*) &gVertices[0].x(),vertStride); bool useQuantizedAabbCompression = true ; trimeshShape = new btBvhTriangleMeshShape(m_indexVertexArrays,useQuantizedAabbCompression); m_collisionShapes.push_back(trimeshShape); btCollisionShape* groundShape = trimeshShape; m_collisionConfiguration = new btDefaultCollisionConfiguration(); m_dispatcher = new btCollisionDispatcher(m_collisionConfiguration); btVector3 worldMin(-1000,-1000,-1000); btVector3 worldMax(1000,1000,1000); m_broadphase = new btAxisSweep3(worldMin,worldMax); m_solver = new btSequentialImpulseConstraintSolver(); m_dynamicsWorld = new btDiscreteDynamicsWorld(m_dispatcher,m_broadphase,m_solver,m_collisionConfiguration); float mass = 0.f; btTransform startTransform; startTransform.setIdentity(); startTransform.setOrigin(btVector3(0,-2,0)); btCollisionShape* colShape = new btBoxShape(btVector3(1,1,1)); m_collisionShapes.push_back(colShape); { for ( int j=0;j<NUM_DYNAMIC_BOXES_X;j++) for ( int i=0;i<NUM_DYNAMIC_BOXES_Y;i++) { //btCollisionShape* colShape = new btCapsuleShape(0.5,2.0);//boxShape = new btSphereShape(1.f); startTransform.setOrigin(btVector3(5*(i-NUM_DYNAMIC_BOXES_X/2),10,5*(j-NUM_DYNAMIC_BOXES_Y/2))); localCreateRigidBody(1, startTransform,colShape); } } startTransform.setIdentity(); //startTransform = btTransform(btQuaternion (btVector3(1,1,1), 1.5)); staticBody = localCreateRigidBody(mass, startTransform,groundShape); staticBody->setCollisionFlags(staticBody->getCollisionFlags() | btCollisionObject::CF_STATIC_OBJECT); //enable custom material callback staticBody->setCollisionFlags(staticBody->getCollisionFlags() | btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK); convexcastBatch = btConvexcastBatch (40.0, 0.0, -10.0,80.0); //convexcastBatch = btConvexcastBatch (true, 40.0, -50.0, 50.0); } void ConcaveConvexcastDemo::clientMoveAndDisplay() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); float dt = getDeltaTimeMicroseconds() * 0.000001f; if (m_animatedMesh) { static float offset=0.f; offset+=0.01f; int i; int j; btVector3 aabbMin(BT_LARGE_FLOAT,BT_LARGE_FLOAT,BT_LARGE_FLOAT); btVector3 aabbMax(-BT_LARGE_FLOAT,-BT_LARGE_FLOAT,-BT_LARGE_FLOAT); for ( i=NUM_VERTS_X/2-3;i<NUM_VERTS_X/2+2;i++) { for (j=NUM_VERTS_X/2-3;j<NUM_VERTS_Y/2+2;j++) { aabbMax.setMax(gVertices[i+j*NUM_VERTS_X]); aabbMin.setMin(gVertices[i+j*NUM_VERTS_X]); gVertices[i+j*NUM_VERTS_X].setValue((i-NUM_VERTS_X*0.5f)*TRIANGLE_SIZE, //0.f, waveheight*sinf(( float )i+offset)*cosf(( float )j+offset), (j-NUM_VERTS_Y*0.5f)*TRIANGLE_SIZE); aabbMin.setMin(gVertices[i+j*NUM_VERTS_X]); aabbMax.setMax(gVertices[i+j*NUM_VERTS_X]); } } trimeshShape->partialRefitTree(aabbMin,aabbMax); //clear all contact points involving mesh proxy. Note: this is a slow/unoptimized operation. m_dynamicsWorld->getBroadphase()->getOverlappingPairCache()->cleanProxyFromPairs(staticBody->getBroadphaseHandle(),getDynamicsWorld()->getDispatcher()); } m_dynamicsWorld->stepSimulation(dt); //optional but useful: debug drawing m_dynamicsWorld->debugDrawWorld(); convexcastBatch.move (dt); convexcastBatch.cast (m_dynamicsWorld); renderme(); convexcastBatch.draw (); glFlush(); glutSwapBuffers(); } void ConcaveConvexcastDemo::displayCallback( void ) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); renderme(); convexcastBatch.draw (); glFlush(); glutSwapBuffers(); } void ConcaveConvexcastDemo::exitPhysics() { //cleanup in the reverse order of creation/initialization //remove the rigidbodies from the dynamics world and delete them int i; for (i=m_dynamicsWorld->getNumCollisionObjects()-1; i>=0 ;i--) { btCollisionObject* obj = m_dynamicsWorld->getCollisionObjectArray()[i]; btRigidBody* body = btRigidBody::upcast(obj); if (body && body->getMotionState()) { delete body->getMotionState(); } m_dynamicsWorld->removeCollisionObject( obj ); delete obj; } //delete collision shapes for ( int j=0;j<m_collisionShapes.size();j++) { btCollisionShape* shape = m_collisionShapes[j]; delete shape; } //delete dynamics world delete m_dynamicsWorld; if (m_indexVertexArrays) delete m_indexVertexArrays; //delete solver delete m_solver; //delete broadphase delete m_broadphase; //delete dispatcher delete m_dispatcher; delete m_collisionConfiguration; } |
いつもの通り、ヘッダは説明要らずなので飛ばしました。
これ読んで実行結果が予想できる人はすごいと思います。(私には無理…)
実行結果はこちら↓
では解説していきます。
最初の initPhysics 関数では、これまでの btCollisionWorld とは違い、
物理演算も行う btDiscreteDynamicsWorld を作成しています。
また、ゆらゆらと揺らめく地面のメッシュとして btBvhTriangleMeshShape を作っています。
これの作り方は簡単
btTriangleIndexVertexArray に頂点バッファとインデックスバッファを渡し
これを btBvhTriangleMeshShape のコンストラクタに渡すだけです。
btBvhTriangleMeshShape ですが、これは btCollisionShape を派生させて作ったクラスです。
一般的な話ですが dynamicsWorld にオブジェクトを追加するには、shape のほかに mass と transform を必要とします。
この操作については、次の関数を用いて行っていました。
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 31 | btRigidBody* DemoApplication::localCreateRigidBody( float mass, const btTransform& startTransform,btCollisionShape* shape) { btAssert((!shape || shape->getShapeType() != INVALID_SHAPE_PROXYTYPE)); //rigidbody is dynamic if and only if mass is non zero, otherwise static bool isDynamic = (mass != 0.f); btVector3 localInertia(0,0,0); if (isDynamic) shape->calculateLocalInertia(mass,localInertia); //using motionstate is recommended, it provides interpolation capabilities, and only synchronizes 'active' objects #define USE_MOTIONSTATE 1 #ifdef USE_MOTIONSTATE btDefaultMotionState* myMotionState = new btDefaultMotionState(startTransform); btRigidBody::btRigidBodyConstructionInfo cInfo(mass,myMotionState,shape,localInertia); btRigidBody* body = new btRigidBody(cInfo); body->setContactProcessingThreshold(m_defaultContactProcessingThreshold); #else btRigidBody* body = new btRigidBody(mass,0,shape,localInertia); body->setWorldTransform(startTransform); #endif// m_dynamicsWorld->addRigidBody(body); return body; } |
戻り値には、実際に dynamicsWorld に追加した剛体へのポインタが返されています。
あとは、無数のボックスをあちこちに追加するコードが書かれています。
これも btCollisionShape の派生クラス btBoxShape を作成して上記の関数で dynamicsWorld へ追加しているだけですね。
ドキュメントを読んでいる方は知っていると思いますが
btCollisonShape は同じものを使うなら、使いまわし可能なのでコイツは一回の作成だけで十分です。
そして今回の肝となる btConvexcastBatch の登場です。
これはこのデモ用に作成されたクラスです。
定義がソースに書かれているので確認してみてください。
コンストラクタで、次の値を設定していますね。(本当、コメント書いていてほしいね…まったく!)
第1引数:コーンの形状の緑色のレイがあると思いますが、これの底面の円の半径[m]
第2引数:緑色のレイの発生源の奥行き位置[m]
第3引数:レイの終端位置の高さ[m]
第4引数:レイの発生源の位置の高さ[m]
つまりですよ、コンストラクタでメンバの100本のレイ(図の緑色のライン)の発生源と照射先の位置を決定していたのです。
メンバ関数の move ですが、ここには x軸方向に往復させるだけの記述しか書かれていませんでした。
さてさて、注目すべきは cast 関数です。
btCollisionWorld::ClosestConvexResultCallback 構造体に、レイの開始位置と終端位置(btVector3)を渡してインスタンスを用意します。
次に X軸での回転を混ぜた(シェイプの底面がレイの方向と垂直になるようにした) from と to の姿勢行列(btTransform)を用意します。
あとは、 btCollisionWorld::convexSweepTest 関数にシェイプと一緒にこれらを渡します。
この関数は内部ですべての衝突オブジェクトと判定を行い、最初にヒットした位置の情報を構造体に入れるというものです。
Sweep って聞いてわかるかな? from の位置から to へ向かってオブジェクトを移動させて
軌跡だけ見たなら如意棒(にょいぼう)のように衝突物体を伸ばしていく操作のことです。
ヒットしたかどうかは hasHit 関数で判定できます。
ヒットした位置は m_hitPointWorld, 法線は m_hitNormalWorld, 開始位置と終端位置までの長さに対する
ヒットした位置までの比は m_closestHitFraction に入ります。
以下、ちょっと冗長かもしれませんが、再度 Sweep Test について説明します。
さっきからレイ(線)と言っていますが、結局のところ指定したシェイプを sweep してヒットするか調べているわけで
厳密なレイではありません。また、sweep とはその方向への引き延ばしを意味します。
用途としてはやはり、進行方向にこのまま進み続けたら、どれくらいで衝突物体に当たるかを調べるときに使えそう
次に勉強するレイキャストとはまた違った「シェイプ」を考慮した SweepCast による衝突判定です。
使いどころがありそうなので、覚えておきましょう。
衝突判定で一番やりたかったこと、それはレイキャスト判定です。
直線を引いて当たった衝突物体とその位置、法線を取得するというもの。
今回はそのレイキャスト判定について学びます。
ではさっそくデモのソースコードを確認してみてください↓
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 | /* * Copyright (c) 2005 Erwin Coumans <www.erwincoumans.com> * * Permission to use, copy, modify, distribute and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appear in all copies. * Erwin Coumans makes no representations about the suitability * of this software for any purpose. * It is provided "as is" without express or implied warranty. */ #include "BulletCollision/CollisionDispatch/btCollisionWorld.h" /* Raytracer uses the Convex rayCast to visualize the Collision Shapes/Minkowski Sum. Very basic raytracer, rendering into a texture. */ ///Low level demo, doesn't include btBulletCollisionCommon.h #include "LinearMath/btQuaternion.h" #include "LinearMath/btTransform.h" #include "GL_ShapeDrawer.h" #include "GLDebugDrawer.h" #include "Raytracer.h" #include "GlutStuff.h" #include "BulletCollision/NarrowPhaseCollision/btVoronoiSimplexSolver.h" #include "BulletCollision/NarrowPhaseCollision/btSubSimplexConvexCast.h" #include "BulletCollision/NarrowPhaseCollision/btGjkConvexCast.h" #include "BulletCollision/NarrowPhaseCollision/btContinuousConvexCollision.h" #include "BulletCollision/CollisionShapes/btSphereShape.h" #include "BulletCollision/CollisionShapes/btMultiSphereShape.h" #include "BulletCollision/CollisionShapes/btConvexHullShape.h" #include "LinearMath/btAabbUtil2.h" #include "BulletCollision/CollisionShapes/btBoxShape.h" #include "BulletCollision/CollisionShapes/btCompoundShape.h" #include "BulletCollision/CollisionShapes/btTetrahedronShape.h" #include "BulletCollision/CollisionShapes/btConeShape.h" #include "BulletCollision/CollisionShapes/btCylinderShape.h" #include "BulletCollision/CollisionShapes/btMinkowskiSumShape.h" #include "BulletCollision/CollisionDispatch/btDefaultCollisionConfiguration.h" #include "BulletCollision/BroadphaseCollision/btAxisSweep3.h" #include "RenderTexture.h" static btVoronoiSimplexSolver simplexSolver; static float yaw=0.f,pitch=0.f,roll=0.f; static const int maxNumObjects = 4; static const int numObjects = 3; static btConvexShape* shapePtr[maxNumObjects]; static btTransform transforms[maxNumObjects]; renderTexture* raytracePicture = 0; //this applies to the raytracer virtual screen/image buffer static int screenWidth = 256; //256; //float aspectRatio = (3.f/4.f); static int screenHeight = 256; //256;//screenWidth * aspectRatio; GLuint glTextureId; btConeShape myCone(1,1); btSphereShape mysphere(1); btBoxShape mybox(btVector3(1,1,1)); btCollisionWorld* m_collisionWorld = 0; /// /// /// void Raytracer::initPhysics() { m_ele = 0; raytracePicture = new renderTexture(screenWidth,screenHeight); myCone.setMargin(0.2f); //choose shape shapePtr[0] = &myCone; shapePtr[1] = &mysphere; shapePtr[2] = &mybox; for ( int i=0;i<numObjects;i++) { transforms[i].setIdentity(); btVector3 pos(0.f,0.f,-(2.5* numObjects * 0.5)+i*2.5f); transforms[i].setIdentity(); transforms[i].setOrigin( pos ); btQuaternion orn; if (i < 2) { orn.setEuler(yaw,pitch,roll); transforms[i].setRotation(orn); } } m_collisionConfiguration = new btDefaultCollisionConfiguration(); m_dispatcher = new btCollisionDispatcher(m_collisionConfiguration); btVector3 worldMin(-1000,-1000,-1000); btVector3 worldMax(1000,1000,1000); m_overlappingPairCache = new btAxisSweep3(worldMin,worldMax); m_collisionWorld = new btCollisionWorld(m_dispatcher,m_overlappingPairCache,m_collisionConfiguration); for ( int s=0;s<numObjects;s++) { btCollisionObject* obj = new btCollisionObject(); obj->setCollisionShape(shapePtr[s]); obj->setWorldTransform(transforms[s]); m_collisionWorld->addCollisionObject(obj); } } Raytracer::~Raytracer() { //cleanup in the reverse order of creation/initialization //remove the rigidbodies from the dynamics world and delete them int i; for (i=m_collisionWorld->getNumCollisionObjects()-1; i>=0 ;i--) { btCollisionObject* obj = m_collisionWorld->getCollisionObjectArray()[i]; m_collisionWorld->removeCollisionObject( obj ); delete obj; } //delete collision world delete m_collisionWorld; //delete broadphase delete m_overlappingPairCache; //delete dispatcher delete m_dispatcher; delete m_collisionConfiguration; delete raytracePicture; raytracePicture=0; } //to be implemented by the demo void Raytracer::clientMoveAndDisplay() { displayCallback(); } bool Raytracer::worldRaytest( const btVector3& rayFrom, const btVector3& rayTo,btVector3& worldNormal,btVector3& worldHitPoint) { struct AllRayResultCallback : public btCollisionWorld::RayResultCallback { AllRayResultCallback( const btVector3& rayFromWorld, const btVector3& rayToWorld) :m_rayFromWorld(rayFromWorld), m_rayToWorld(rayToWorld) { } btVector3 m_rayFromWorld; //used to calculate hitPointWorld from hitFraction btVector3 m_rayToWorld; btVector3 m_hitNormalWorld; btVector3 m_hitPointWorld; virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) { //caller already does the filter on the m_closestHitFraction btAssert(rayResult.m_hitFraction <= m_closestHitFraction); m_closestHitFraction = rayResult.m_hitFraction; m_collisionObject = rayResult.m_collisionObject; if (normalInWorldSpace) { m_hitNormalWorld = rayResult.m_hitNormalLocal; } else { ///need to transform normal into worldspace m_hitNormalWorld = m_collisionObject->getWorldTransform().getBasis()*rayResult.m_hitNormalLocal; } m_hitPointWorld.setInterpolate3(m_rayFromWorld,m_rayToWorld,rayResult.m_hitFraction); return 1.f; } }; AllRayResultCallback resultCallback(rayFrom,rayTo); // btCollisionWorld::ClosestRayResultCallback resultCallback(rayFrom,rayTo); m_collisionWorld->rayTest(rayFrom,rayTo,resultCallback); if (resultCallback.hasHit()) { worldNormal = resultCallback.m_hitNormalWorld; return true ; } return false ; } bool Raytracer::singleObjectRaytest( const btVector3& rayFrom, const btVector3& rayTo,btVector3& worldNormal,btVector3& worldHitPoint) { btScalar closestHitResults = 1.f; btCollisionWorld::ClosestRayResultCallback resultCallback(rayFrom,rayTo); bool hasHit = false ; btConvexCast::CastResult rayResult; btSphereShape pointShape(0.0f); btTransform rayFromTrans; btTransform rayToTrans; rayFromTrans.setIdentity(); rayFromTrans.setOrigin(rayFrom); rayToTrans.setIdentity(); rayToTrans.setOrigin(rayTo); for ( int s=0;s<numObjects;s++) { //comment-out next line to get all hits, instead of just the closest hit //resultCallback.m_closestHitFraction = 1.f; //do some culling, ray versus aabb btVector3 aabbMin,aabbMax; shapePtr[s]->getAabb(transforms[s],aabbMin,aabbMax); btScalar hitLambda = 1.f; btVector3 hitNormal; btCollisionObject tmpObj; tmpObj.setWorldTransform(transforms[s]); if (btRayAabb(rayFrom,rayTo,aabbMin,aabbMax,hitLambda,hitNormal)) { //reset previous result btCollisionWorld::rayTestSingle(rayFromTrans,rayToTrans, &tmpObj, shapePtr[s], transforms[s], resultCallback); if (resultCallback.hasHit()) { //float fog = 1.f - 0.1f * rayResult.m_fraction; resultCallback.m_hitNormalWorld.normalize(); //.m_normal.normalize(); worldNormal = resultCallback.m_hitNormalWorld; //worldNormal = transforms[s].getBasis() *rayResult.m_normal; worldNormal.normalize(); hasHit = true ; } } } return hasHit; } bool Raytracer::lowlevelRaytest( const btVector3& rayFrom, const btVector3& rayTo,btVector3& worldNormal,btVector3& worldHitPoint) { btScalar closestHitResults = 1.f; bool hasHit = false ; btConvexCast::CastResult rayResult; btSphereShape pointShape(0.0f); btTransform rayFromTrans; btTransform rayToTrans; rayFromTrans.setIdentity(); rayFromTrans.setOrigin(rayFrom); rayToTrans.setIdentity(); rayToTrans.setOrigin(rayTo); for ( int s=0;s<numObjects;s++) { //do some culling, ray versus aabb btVector3 aabbMin,aabbMax; shapePtr[s]->getAabb(transforms[s],aabbMin,aabbMax); btScalar hitLambda = 1.f; btVector3 hitNormal; btCollisionObject tmpObj; tmpObj.setWorldTransform(transforms[s]); if (btRayAabb(rayFrom,rayTo,aabbMin,aabbMax,hitLambda,hitNormal)) { //reset previous result //choose the continuous collision detection method btSubsimplexConvexCast convexCaster(&pointShape,shapePtr[s],&simplexSolver); //btGjkConvexCast convexCaster(&pointShape,shapePtr[s],&simplexSolver); //btContinuousConvexCollision convexCaster(&pointShape,shapePtr[s],&simplexSolver,0); if (convexCaster.calcTimeOfImpact(rayFromTrans,rayToTrans,transforms[s],transforms[s],rayResult)) { if (rayResult.m_fraction < closestHitResults) { closestHitResults = rayResult.m_fraction; worldNormal = transforms[s].getBasis() *rayResult.m_normal; worldNormal.normalize(); hasHit = true ; } } } } return hasHit; } void Raytracer::displayCallback() { updateCamera(); for ( int i=0;i<numObjects;i++) { transforms[i].setIdentity(); btVector3 pos(0.f,0.f,-(2.5* numObjects * 0.5)+i*2.5f); transforms[i].setOrigin( pos ); btQuaternion orn; if (i < 2) { orn.setEuler(yaw,pitch,roll); transforms[i].setRotation(orn); } } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDisable(GL_LIGHTING); if (!m_initialized) { m_initialized = true ; glGenTextures(1, &glTextureId); } glBindTexture(GL_TEXTURE_2D,glTextureId ); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); btVector4 rgba(1.f,0.f,0.f,0.5f); float top = 1.f; float bottom = -1.f; float nearPlane = 1.f; float tanFov = (top-bottom)*0.5f / nearPlane; float fov = 2.0 * atanf (tanFov); btVector3 rayFrom = getCameraPosition(); btVector3 rayForward = getCameraTargetPosition()-getCameraPosition(); rayForward.normalize(); float farPlane = 600.f; rayForward*= farPlane; btVector3 rightOffset; btVector3 vertical(0.f,1.f,0.f); btVector3 hor; hor = rayForward.cross(vertical); hor.normalize(); vertical = hor.cross(rayForward); vertical.normalize(); float tanfov = tanf(0.5f*fov); hor *= 2.f * farPlane * tanfov; vertical *= 2.f * farPlane * tanfov; btVector3 rayToCenter = rayFrom + rayForward; btVector3 dHor = hor * 1.f/ float (screenWidth); btVector3 dVert = vertical * 1.f/ float (screenHeight); btTransform rayFromTrans; rayFromTrans.setIdentity(); rayFromTrans.setOrigin(rayFrom); btTransform rayFromLocal; btTransform rayToLocal; int x; ///clear texture for (x=0;x<screenWidth;x++) { for ( int y=0;y<screenHeight;y++) { btVector4 rgba(0.2f,0.2f,0.2f,1.f); raytracePicture->setPixel(x,y,rgba); } } #if 1 btVector3 rayTo; btTransform colObjWorldTransform; colObjWorldTransform.setIdentity(); int mode = 0; for (x=0;x<screenWidth;x++) { for ( int y=0;y<screenHeight;y++) { rayTo = rayToCenter - 0.5f * hor + 0.5f * vertical; rayTo += x * dHor; rayTo -= y * dVert; btVector3 worldNormal(0,0,0); btVector3 worldPoint(0,0,0); bool hasHit = false ; int mode = 2; switch (mode) { case 0: hasHit = lowlevelRaytest(rayFrom,rayTo,worldNormal,worldPoint); break ; case 1: hasHit = singleObjectRaytest(rayFrom,rayTo,worldNormal,worldPoint); break ; case 2: hasHit = worldRaytest(rayFrom,rayTo,worldNormal,worldPoint); break ; default : { } } if (hasHit) { float lightVec0 = worldNormal.dot(btVector3(0,-1,-1)); //0.4f,-1.f,-0.4f)); float lightVec1= worldNormal.dot(btVector3(-1,0,-1)); //-0.4f,-1.f,-0.4f)); rgba = btVector4(lightVec0,lightVec1,0,1.f); rgba.setMin(btVector3(1,1,1)); rgba.setMax(btVector3(0.2,0.2,0.2)); rgba[3] = 1.f; raytracePicture->setPixel(x,y,rgba); } else btVector4 rgba = raytracePicture->getPixel(x,y); if (!rgba.length2()) { raytracePicture->setPixel(x,y,btVector4(1,1,1,1)); } } } #endif extern unsigned char sFontData[]; if (0) { const char * text= "ABC abc 123 !@#" ; int x=0; for ( int cc = 0;cc< strlen (text);cc++) { char testChar = text[cc]; //'b'; char ch = testChar-32; int startx=ch%16; int starty=ch/16; //for (int i=0;i<256;i++) for ( int i=startx*16;i<(startx*16+16);i++) { int y=0; //for (int j=0;j<256;j++) //for (int j=0;j<256;j++) for ( int j=starty*16;j<(starty*16+16);j++) { btVector4 rgba(0,0,0,1); rgba[0] = (sFontData[i*3+255*256*3-(256*j)*3])/255.f; //rgba[0] += (sFontData[(i+1)*3+255*256*3-(256*j)*3])/255.*0.25f; //rgba[0] += (sFontData[(i)*3+255*256*3-(256*j+1)*3])/255.*0.25f; //rgba[0] += (sFontData[(i+1)*3+255*256*3-(256*j+1)*3])/255.*0.25; //if (rgba[0]!=0.f) { rgba[1]=rgba[0]; rgba[2]=rgba[0]; rgba[3]=1.f; //raytracePicture->setPixel(x,y,rgba); raytracePicture->addPixel(x,y,rgba); } y++; } x++; } } } //raytracePicture->grapicalPrintf("CCD RAYTRACER",sFontData); char buffer[256]; sprintf (buffer, "%d rays" ,screenWidth*screenHeight*numObjects); //sprintf(buffer,"Toggle",screenWidth*screenHeight*numObjects); //sprintf(buffer,"TEST",screenWidth*screenHeight*numObjects); //raytracePicture->grapicalPrintf(buffer,sFontData,0,10);//&BMF_font_helv10,0,10); raytracePicture->grapicalPrintf(buffer,sFontData,0,0); //&BMF_font_helv10,0,10); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glFrustum(-1.0,1.0,-1.0,1.0,3,2020.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); // reset The Modelview Matrix glTranslatef(0.0f,0.0f,-3.1f); // Move Into The Screen 5 Units glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D,glTextureId ); const unsigned char *ptr = raytracePicture->getBuffer(); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, raytracePicture->getWidth(),raytracePicture->getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, ptr); glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4f (1,1,1,1); // alpha=0.5=half visible glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex2f(-1,1); glTexCoord2f(1.0f, 0.0f); glVertex2f(1,1); glTexCoord2f(1.0f, 1.0f); glVertex2f(1,-1); glTexCoord2f(0.0f, 1.0f); glVertex2f(-1,-1); glEnd(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glDisable(GL_TEXTURE_2D); glDisable(GL_DEPTH_TEST); GL_ShapeDrawer::drawCoordSystem(); { for ( int i=0;i<numObjects;i++) { btVector3 aabbMin,aabbMax; shapePtr[i]->getAabb(transforms[i],aabbMin,aabbMax); } } glPushMatrix(); glPopMatrix(); pitch += 0.005f; yaw += 0.01f; m_azi += 1.f; glFlush(); glutSwapBuffers(); } |
実行結果はこんな感じになります。
では、ソースコードにハイライトを入れている void Raytracer::displayCallback() 関数にご注目ください。
次のコードにて、一度画面をクリアした後にカメラ位置を始点に
すべての画素(ピクセル位置)に向かってレイを放っていることがうかがえます。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | ///clear texture for (x=0;x<screenWidth;x++) { for ( int y=0;y<screenHeight;y++) { btVector4 rgba(0.2f,0.2f,0.2f,1.f); raytracePicture->setPixel(x,y,rgba); } } #if 1 btVector3 rayTo; btTransform colObjWorldTransform; colObjWorldTransform.setIdentity(); int mode = 0; for (x=0;x<screenWidth;x++) { for ( int y=0;y<screenHeight;y++) { rayTo = rayToCenter - 0.5f * hor + 0.5f * vertical; rayTo += x * dHor; rayTo -= y * dVert; btVector3 worldNormal(0,0,0); btVector3 worldPoint(0,0,0); bool hasHit = false ; int mode = 2; switch (mode) { case 0: hasHit = lowlevelRaytest(rayFrom,rayTo,worldNormal,worldPoint); break ; case 1: hasHit = singleObjectRaytest(rayFrom,rayTo,worldNormal,worldPoint); break ; case 2: hasHit = worldRaytest(rayFrom,rayTo,worldNormal,worldPoint); break ; default : { } } if (hasHit) { float lightVec0 = worldNormal.dot(btVector3(0,-1,-1)); //0.4f,-1.f,-0.4f)); float lightVec1= worldNormal.dot(btVector3(-1,0,-1)); //-0.4f,-1.f,-0.4f)); rgba = btVector4(lightVec0,lightVec1,0,1.f); rgba.setMin(btVector3(1,1,1)); rgba.setMax(btVector3(0.2,0.2,0.2)); rgba[3] = 1.f; raytracePicture->setPixel(x,y,rgba); } else btVector4 rgba = raytracePicture->getPixel(x,y); if (!rgba.length2()) { raytracePicture->setPixel(x,y,btVector4(1,1,1,1)); } } } #endif |
レイを放っている領域にはコーンと球とボックスの衝突物体が置かれていまして
オブジェクト全体が Y軸でゆっくりと回転していくようにコーディングされています。
衝突判定をこれら画素ごとのレイすべてに対して行い、衝突していた場合は法線方向から色を計算して画素の色を設定しています。
といったデモなので、どうやってその衝突位置と法線を求めたのか見ていきたいと思います。
lowlevelRaytest, singleObjectRaytest, worldRaytest 関数の中身を順にのぞいてみましょう。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | bool Raytracer::lowlevelRaytest( const btVector3& rayFrom, const btVector3& rayTo,btVector3& worldNormal,btVector3& worldHitPoint) { btScalar closestHitResults = 1.f; bool hasHit = false ; btConvexCast::CastResult rayResult; btSphereShape pointShape(0.0f); btTransform rayFromTrans; btTransform rayToTrans; rayFromTrans.setIdentity(); rayFromTrans.setOrigin(rayFrom); rayToTrans.setIdentity(); rayToTrans.setOrigin(rayTo); for ( int s=0;s<numObjects;s++) { //do some culling, ray versus aabb btVector3 aabbMin,aabbMax; shapePtr[s]->getAabb(transforms[s],aabbMin,aabbMax); btScalar hitLambda = 1.f; btVector3 hitNormal; btCollisionObject tmpObj; tmpObj.setWorldTransform(transforms[s]); if (btRayAabb(rayFrom,rayTo,aabbMin,aabbMax,hitLambda,hitNormal)) { //reset previous result //choose the continuous collision detection method btSubsimplexConvexCast convexCaster(&pointShape,shapePtr[s],&simplexSolver); //btGjkConvexCast convexCaster(&pointShape,shapePtr[s],&simplexSolver); //btContinuousConvexCollision convexCaster(&pointShape,shapePtr[s],&simplexSolver,0); if (convexCaster.calcTimeOfImpact(rayFromTrans,rayToTrans,transforms[s],transforms[s],rayResult)) { if (rayResult.m_fraction < closestHitResults) { closestHitResults = rayResult.m_fraction; worldNormal = transforms[s].getBasis() *rayResult.m_normal; worldNormal.normalize(); hasHit = true ; } } } } return hasHit; } |
グローバル変数を利用しているから可読性は低いのですが、わかっていることだけお伝えします。
初期化の initPhysics 関数でグローバルの transforms 配列と shapePtr 配列は既に設定済みです。
それぞれには衝突形状のコーンと球とボックスの姿勢と形状情報が入っています。
それを踏まえて読んでいってください。
まずは当たり判定の対象の姿勢から getAabb 関数で AABB最小値と AABB最大値を取得しています。
AABBとは 境界ボックス:Axis-Aligned Bounding Box のことです。
AABBがわからない?接触しているかどうかを簡単に知るための図形でして、基本なのでここで覚えておきましょう。
ひとまず btRayAabb 関数を使って大域処理にてヒットする可能性を探ります。(AABB判定なので高速)
当たっているかもしれないとき(true を返したとき)、btSubsimplexConvexCast を作成しています。
コンストラクタで半径が0の球と当たり判定を行う形状、そして btVoronoiSimplexSolver を渡しています。
次に、レイの始点と終点姿勢と判定対象の始点と終点の姿勢(同じ姿勢を渡しているので今当たっているかどうかを返すはず)を
btConvexCast::calcTimeOfImpact 関数に渡して、詳細な判定結果を取得します。
この関数の戻り値はヒットしている場合 true を返します。
参照引数の btConvexCast::CastResult 構造体に目的のレイキャストの結果が含まれています。
さて、当たり判定を行うオブジェクトは一つではないので、一番最初にレイがヒットしたオブジェクトを特定するため
closestHitResults 変数を利用して判定します。レイの視点から衝突点までの比率が最も小さいものを選択するためです。
ワールド座標系における、衝突点の法線を求めるにはオブジェクトのベース行列をかけないといけない模様です。
この辺は、コードを参照してください。
びっくりする話、ワールド座標系における衝突点の位置はすっぽかしている模様。(なんてデモじゃ!)
そのほかのレイキャスト方法も気になるので
続いて singleObjectRaytest 関数を見てみましょう。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | bool Raytracer::singleObjectRaytest( const btVector3& rayFrom, const btVector3& rayTo,btVector3& worldNormal,btVector3& worldHitPoint) { btScalar closestHitResults = 1.f; btCollisionWorld::ClosestRayResultCallback resultCallback(rayFrom,rayTo); bool hasHit = false ; btConvexCast::CastResult rayResult; btSphereShape pointShape(0.0f); btTransform rayFromTrans; btTransform rayToTrans; rayFromTrans.setIdentity(); rayFromTrans.setOrigin(rayFrom); rayToTrans.setIdentity(); rayToTrans.setOrigin(rayTo); for ( int s=0;s<numObjects;s++) { //comment-out next line to get all hits, instead of just the closest hit //resultCallback.m_closestHitFraction = 1.f; //do some culling, ray versus aabb btVector3 aabbMin,aabbMax; shapePtr[s]->getAabb(transforms[s],aabbMin,aabbMax); btScalar hitLambda = 1.f; btVector3 hitNormal; btCollisionObject tmpObj; tmpObj.setWorldTransform(transforms[s]); if (btRayAabb(rayFrom,rayTo,aabbMin,aabbMax,hitLambda,hitNormal)) { //reset previous result btCollisionWorld::rayTestSingle(rayFromTrans,rayToTrans, &tmpObj, shapePtr[s], transforms[s], resultCallback); if (resultCallback.hasHit()) { //float fog = 1.f - 0.1f * rayResult.m_fraction; resultCallback.m_hitNormalWorld.normalize(); //.m_normal.normalize(); worldNormal = resultCallback.m_hitNormalWorld; //worldNormal = transforms[s].getBasis() *rayResult.m_normal; worldNormal.normalize(); hasHit = true ; } } } return hasHit; } |
先ほどと違う点と言えば、btCollisionWorld::rayTestSingle で btCollisionWorld::ClosestRayResultCallback
を得ているあたりですね。hasHit 関数で詳細判定で当たっているかどうか調べています。
あれ?一番最初に当たったかどうかの判定が抜けてますね。
とにかく、最後に当たっていると判定した衝突物体との衝突点における法線を返します。
これは法線にベース行列を掛けなくて良い模様。
最後に worldRaytest 関数を見てみましょう。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | bool Raytracer::worldRaytest( const btVector3& rayFrom, const btVector3& rayTo,btVector3& worldNormal,btVector3& worldHitPoint) { struct AllRayResultCallback : public btCollisionWorld::RayResultCallback { AllRayResultCallback( const btVector3& rayFromWorld, const btVector3& rayToWorld) :m_rayFromWorld(rayFromWorld), m_rayToWorld(rayToWorld) { } btVector3 m_rayFromWorld; //used to calculate hitPointWorld from hitFraction btVector3 m_rayToWorld; btVector3 m_hitNormalWorld; btVector3 m_hitPointWorld; virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) { //caller already does the filter on the m_closestHitFraction btAssert(rayResult.m_hitFraction <= m_closestHitFraction); m_closestHitFraction = rayResult.m_hitFraction; m_collisionObject = rayResult.m_collisionObject; if (normalInWorldSpace) { m_hitNormalWorld = rayResult.m_hitNormalLocal; } else { ///need to transform normal into worldspace m_hitNormalWorld = m_collisionObject->getWorldTransform().getBasis()*rayResult.m_hitNormalLocal; } m_hitPointWorld.setInterpolate3(m_rayFromWorld,m_rayToWorld,rayResult.m_hitFraction); return 1.f; } }; AllRayResultCallback resultCallback(rayFrom,rayTo); // btCollisionWorld::ClosestRayResultCallback resultCallback(rayFrom,rayTo); m_collisionWorld->rayTest(rayFrom,rayTo,resultCallback); if (resultCallback.hasHit()) { worldNormal = resultCallback.m_hitNormalWorld; return true ; } return false ; } |
btCollisionWorld::RayResultCallback 構造体を継承した構造体を定義し
addSingleResult 関数をオーバーライドしています。
この関数内を見てみると m_hitNormalWorld に結果を代入しているのがわかります。
これが使い方が一番単純でわかりやすいかもしれません
btCollisionWorld::rayTest 関数で コールバックを取得し
hasHit で衝突の有無を判断して、衝突点の法線を取得しています。
ただし、記述が簡単な半面、これは btCollisionWorld の衝突物体すべてに対して当たり判定を行っているため
これまで見てきた判定オブジェクトを指定する方法と違って、無関係の衝突物体が多数ある場合は
パフォーマンスが出ないと思います。
いかがでしたでしょうか?
レイキャスト判定にもいくつか手法があって、それぞれの具体的な使い方が明らかになったと思います。
レイキャストの実装の予備知識としてこの辺は忘れないようにしておきたいところですね。
次のデモは衝突予測に使えるデモです。
早速確認してみたいと思います。
物体と物体がそれぞれ移動していて、あるとき衝突する現象を想像してください。
たとえば boxA が1秒後に回転しながら遠くに移動して、その進路を遮るように
boxBが横切るとします。それぞれの速度、回転速度が決まっていた場合衝突が予測できるでしょう。
いつ、どんな状況でぶつかるのか?
それを教えてくれるのが今回のデモです。
さて、今回のコードはそんなに長くないです。
上記のイメージで読んでみて下さい。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 | /* * Copyright (c) 2005 Erwin Coumans http://continuousphysics.com/Bullet/ * * Permission to use, copy, modify, distribute and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appear in all copies. * Erwin Coumans makes no representations about the suitability * of this software for any purpose. * It is provided "as is" without express or implied warranty. */ /* Continuous Convex Collision Demo demonstrates an efficient continuous collision detection algorithm. Both linear and angular velocities are supported. Convex Objects are sampled using Supporting Vertex. Motion using Exponential Map. Future ideas: Comparison with Screwing Motion. Also comparision with Algebraic CCD and Interval Arithmetic methods (Stephane Redon) */ ///This low level demo need internal access, and intentionally doesn't include the btBulletCollisionCommon.h headerfile #include "LinearMath/btQuaternion.h" #include "LinearMath/btTransform.h" #include "BulletCollision/NarrowPhaseCollision/btVoronoiSimplexSolver.h" #include "BulletCollision/CollisionShapes/btBoxShape.h" #include "BulletCollision/CollisionShapes/btMinkowskiSumShape.h" #include "BulletCollision/NarrowPhaseCollision/btGjkPairDetector.h" #include "BulletCollision/NarrowPhaseCollision/btGjkConvexCast.h" #include "BulletCollision/NarrowPhaseCollision/btSubSimplexConvexCast.h" #include "BulletCollision/NarrowPhaseCollision/btContinuousConvexCollision.h" #include "LinearMath/btTransformUtil.h" #include "DebugCastResult.h" #include "BulletCollision/CollisionShapes/btSphereShape.h" #include "BulletCollision/CollisionShapes/btTetrahedronShape.h" #include "BulletCollision/NarrowPhaseCollision/btVoronoiSimplexSolver.h" #include "BulletCollision/NarrowPhaseCollision/btConvexPenetrationDepthSolver.h" #include "GL_ShapeDrawer.h" #include "ContinuousConvexCollision.h" #include "GlutStuff.h" float yaw=0.f,pitch=0.f,roll=0.f; const int maxNumObjects = 4; const int numObjects = 2; btVector3 angVels[numObjects]; btVector3 linVels[numObjects]; btPolyhedralConvexShape* shapePtr[maxNumObjects]; btTransform fromTrans[maxNumObjects]; btTransform toTrans[maxNumObjects]; int screenWidth = 640; int screenHeight = 480; int main( int argc, char ** argv) { btContinuousConvexCollisionDemo* ccdDemo = new btContinuousConvexCollisionDemo(); ccdDemo->setCameraDistance(40.f); ccdDemo->initPhysics(); return glutmain(argc, argv,screenWidth,screenHeight, "Continuous Convex Collision Demo" ,ccdDemo); } void btContinuousConvexCollisionDemo::initPhysics() { fromTrans[0].setOrigin(btVector3(0,10,20)); toTrans[0].setOrigin(btVector3(0,10,-20)); fromTrans[1].setOrigin(btVector3(-2,7,0)); toTrans[1].setOrigin(btVector3(-2,10,0)); btMatrix3x3 identBasis; identBasis.setIdentity(); btMatrix3x3 basisA; basisA.setIdentity(); basisA.setEulerZYX(0.f,-SIMD_HALF_PI,0.f); fromTrans[0].setBasis(identBasis); toTrans[0].setBasis(basisA); fromTrans[1].setBasis(identBasis); toTrans[1].setBasis(identBasis); toTrans[1].setBasis(identBasis); btVector3 boxHalfExtentsA(10,1,1); btVector3 boxHalfExtentsB(1.1f,1.1f,1.1f); btBoxShape* boxA = new btBoxShape(boxHalfExtentsA); // btBU_Simplex1to4* boxA = new btBU_Simplex1to4(btVector3(-2,0,-2),btVector3(2,0,-2),btVector3(0,0,2),btVector3(0,2,0)); // btBU_Simplex1to4* boxA = new btBU_Simplex1to4(btVector3(-12,0,0),btVector3(12,0,0)); btBoxShape* boxB = new btBoxShape(boxHalfExtentsB); shapePtr[0] = boxA; shapePtr[1] = boxB; shapePtr[0]->setMargin(0.01f); shapePtr[1]->setMargin(0.01f); for ( int i=0;i<numObjects;i++) { btTransformUtil::calculateVelocity(fromTrans[i],toTrans[i],1.f,linVels[i],angVels[i]); } } //to be implemented by the demo void btContinuousConvexCollisionDemo::clientMoveAndDisplay() { displayCallback(); } static btVoronoiSimplexSolver sVoronoiSimplexSolver; btSimplexSolverInterface& gGjkSimplexSolver = sVoronoiSimplexSolver; bool drawLine= false ; int minlines = 0; int maxlines = 512; void btContinuousConvexCollisionDemo::displayCallback( void ) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDisable(GL_LIGHTING); //GL_ShapeDrawer::drawCoordSystem(); btScalar m[16]; int i; btVector3 worldBoundsMin(-1000,-1000,-1000); btVector3 worldBoundsMax(1000,1000,1000); /*for (i=0;i<numObjects;i++) { fromTrans[i].getOpenGLMatrix( m ); m_shapeDrawer.drawOpenGL(m,shapePtr[i]); } */ if (getDebugMode()==btIDebugDraw::DBG_DrawAabb) { i=0; //for (i=1;i<numObjects;i++) { //for each object, subdivide the from/to transform in 10 equal steps int numSubSteps = 10; for ( int s=0;s<10;s++) { btScalar subStep = s * 1.f/( float )numSubSteps; btTransform interpolatedTrans; btTransformUtil::integrateTransform(fromTrans[i],linVels[i],angVels[i],subStep,interpolatedTrans); //fromTrans[i].getOpenGLMatrix(m); //m_shapeDrawer.drawOpenGL(m,shapePtr[i]); //toTrans[i].getOpenGLMatrix(m); //m_shapeDrawer.drawOpenGL(m,shapePtr[i]); interpolatedTrans.getOpenGLMatrix( m ); m_shapeDrawer->drawOpenGL(m,shapePtr[i],btVector3(1,0,1),getDebugMode(),worldBoundsMin,worldBoundsMax); } } } btMatrix3x3 mat; mat.setEulerZYX(yaw,pitch,roll); btQuaternion orn; mat.getRotation(orn); orn.setEuler(yaw,pitch,roll); fromTrans[1].setRotation(orn); toTrans[1].setRotation(orn); if (m_stepping || m_singleStep) { m_singleStep = false ; pitch += 0.005f; // yaw += 0.01f; } // btVector3 fromA(-25,11,0); // btVector3 toA(-15,11,0); // btQuaternion ornFromA(0.f,0.f,0.f,1.f); // btQuaternion ornToA(0.f,0.f,0.f,1.f); // btTransform rayFromWorld(ornFromA,fromA); // btTransform rayToWorld(ornToA,toA); btTransform rayFromWorld = fromTrans[0]; btTransform rayToWorld = toTrans[0]; if (drawLine) { glBegin(GL_LINES); glColor3f(0, 0, 1); glVertex3d(rayFromWorld.getOrigin().x(), rayFromWorld.getOrigin().y(),rayFromWorld.getOrigin().z()); glVertex3d(rayToWorld.getOrigin().x(),rayToWorld.getOrigin().y(),rayToWorld.getOrigin().z()); glEnd(); } //now perform a raycast on the shapes, in local (shape) space gGjkSimplexSolver.reset(); //choose one of the following lines for (i=0;i<numObjects;i++) { fromTrans[i].getOpenGLMatrix(m); m_shapeDrawer->drawOpenGL(m,shapePtr[i],btVector3(1,1,1),getDebugMode(),worldBoundsMin,worldBoundsMax); } btDebugCastResult rayResult1(fromTrans[0],shapePtr[0],linVels[0],angVels[0],m_shapeDrawer); for (i=1;i<numObjects;i++) { btConvexCast::CastResult rayResult2; btConvexCast::CastResult* rayResultPtr; if (btIDebugDraw::DBG_DrawAabb) { rayResultPtr = &rayResult1; } else { rayResultPtr = &rayResult2; } //GjkConvexCast convexCaster(&gGjkSimplexSolver); //SubsimplexConvexCast convexCaster(&gGjkSimplexSolver); //optional btConvexPenetrationDepthSolver* penetrationDepthSolver = 0; btContinuousConvexCollision convexCaster(shapePtr[0],shapePtr[i],&gGjkSimplexSolver,penetrationDepthSolver ); gGjkSimplexSolver.reset(); if (convexCaster.calcTimeOfImpact(fromTrans[0],toTrans[0],fromTrans[i] ,toTrans[i] ,*rayResultPtr)) { glDisable(GL_DEPTH_TEST); btTransform hitTrans; btTransformUtil::integrateTransform(fromTrans[0],linVels[0],angVels[0],rayResultPtr->m_fraction,hitTrans); hitTrans.getOpenGLMatrix(m); m_shapeDrawer->drawOpenGL(m,shapePtr[0],btVector3(0,1,0),getDebugMode(),worldBoundsMin,worldBoundsMax); btTransformUtil::integrateTransform(fromTrans[i],linVels[i],angVels[i],rayResultPtr->m_fraction,hitTrans); hitTrans.getOpenGLMatrix(m); m_shapeDrawer->drawOpenGL(m,shapePtr[i],btVector3(0,1,1),getDebugMode(),worldBoundsMin,worldBoundsMax); } } swapBuffers(); } |
実行結果はこんな感じです↓
ピンク?色で表示されているオブジェクトが、始点と終点間を移動しているバーの軌跡(予測位置)です。
(白色の画面右上のバーが始点の姿勢ですね。中央のボックスも白色のが確認できると思いますが、
これは横切るボックスの始点の姿勢です。
ボックスも奥から手前に向かってゆっくり移動し、バーの進路を遮る形になっています。)
読んでその通りなのですが、次のコードでバーの開始位置と終了位置の姿勢を決定しています。
1 2 3 4 5 6 7 8 9 10 11 12 | fromTrans[0].setOrigin(btVector3(0,10,20)); toTrans[0].setOrigin(btVector3(0,10,-20)); btMatrix3x3 identBasis; identBasis.setIdentity(); btMatrix3x3 basisA; basisA.setIdentity(); basisA.setEulerZYX(0.f,-SIMD_HALF_PI,0.f); fromTrans[0].setBasis(identBasis); toTrans[0].setBasis(basisA); |
これを補間するように、速度と回転速度を求める便利機能が次の calculateVelocity です。
1 | btTransformUtil::calculateVelocity(fromTrans[0],toTrans[0],1.f,linVels[0],angVels[0]); |
タイムステップごとに、姿勢を求める便利機能は次の integrateTransform ですね。
1 2 3 4 5 6 7 8 | int numSubSteps = 10; for ( int s=0;s<numSubSteps;s++) { btScalar subStep = s * 1.f/( float )numSubSteps; btTransform interpolatedTrans; btTransformUtil::integrateTransform(fromTrans[0],linVels[0],angVels[0],subStep,interpolatedTrans); … |
これで簡単に任意のタイムステップにおける物体の姿勢が取得できます。
アニメーションしている物体の姿勢予測には便利な機能かもしれませんね。
そして、今回の肝となる衝突位置を返すコードが次の箇所です。
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 31 32 33 34 35 36 37 38 39 | btConvexCast::CastResult rayResult2; btConvexCast::CastResult* rayResultPtr; if (btIDebugDraw::DBG_DrawAabb) { rayResultPtr = &rayResult1; } else { rayResultPtr = &rayResult2; } //GjkConvexCast convexCaster(&gGjkSimplexSolver); //SubsimplexConvexCast convexCaster(&gGjkSimplexSolver); //optional btConvexPenetrationDepthSolver* penetrationDepthSolver = 0; btContinuousConvexCollision convexCaster(shapePtr[0],shapePtr[1],&gGjkSimplexSolver,penetrationDepthSolver ); gGjkSimplexSolver.reset(); if (convexCaster.calcTimeOfImpact(fromTrans[0],toTrans[0],fromTrans[1] ,toTrans[1] ,*rayResultPtr)) { glDisable(GL_DEPTH_TEST); btTransform hitTrans; btTransformUtil::integrateTransform(fromTrans[0],linVels[0],angVels[0],rayResultPtr->m_fraction,hitTrans); hitTrans.getOpenGLMatrix(m); m_shapeDrawer->drawOpenGL(m,shapePtr[0],btVector3(0,1,0),getDebugMode(),worldBoundsMin,worldBoundsMax); btTransformUtil::integrateTransform(fromTrans[1],linVels[1],angVels[1],rayResultPtr->m_fraction,hitTrans); hitTrans.getOpenGLMatrix(m); m_shapeDrawer->drawOpenGL(m,shapePtr[1],btVector3(0,1,1),getDebugMode(),worldBoundsMin,worldBoundsMax); } |
btContinuousConvexCollision を衝突に使う2つのシェイプと任意の solver から作成し
2つのオブジェクトそれぞれの始点と終点の姿勢から calcTimeOfImpact 関数で衝突結果を取得します。
(衝突する場合、関数は true を返します。ていうか、この感じの使い方もう何回か出てきていますね。)
結果に入っている、始点と終点間の内分比を使って、衝突時のバーとボックスを描画しているコードも書かれていますね。
実行結果の緑色の物体が衝突時のバーで水色の物体が衝突時のボックスです。
そのほかのコードは、ボックスの開始と終点における回転を決めるものだったりします。
赤色の物体ですが、これは rayResult1 を利用すると表示されます。
Drawer を渡しているでしょう。それが原因です。
詳細についてはドキュメントが無いからAPIリファレンスでソースを追わないとちょっとわかりません。
興味があったら調べてください。(あんまり重要ではないです。)
ビリヤードの球が当たる場所を予測して表示するとか、使い方は考えればいろいろありそうなデモですね。
今回の衝突予測判定方法も、覚えておこうと思います。
衝突判定のアルゴリズムをユーザから設定することもできます。
厳密な判定ではなく、速度重視のものに変更するなど、パフォーマンスを求めるときなどに
あったらいいなと思う機能です。
サンプルでは球体同士の当たり判定にユーザ独自のアルゴリズムを設定する例が示されています。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | /* Bullet Continuous Collision Detection and Physics Library Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include "btBulletDynamicsCommon.h" #include "LinearMath/btIDebugDraw.h" #include "GLDebugDrawer.h" #include "UserCollisionAlgorithm.h" #include "GL_ShapeDrawer.h" #include "GlutStuff.h" //The user defined collision algorithm #include "BulletCollision/CollisionDispatch/btSphereSphereCollisionAlgorithm.h" GLDebugDrawer debugDrawer; static const int NUM_VERTICES = 5; static const int NUM_TRIANGLES=4; btVector3 gVertices[NUM_VERTICES]; int gIndices[NUM_TRIANGLES*3]; const float TRIANGLE_SIZE=10.f; ///User can override this material combiner by implementing gContactAddedCallback and setting body0->m_collisionFlags |= btCollisionObject::customMaterialCallback; inline btScalar calculateCombinedFriction( float friction0, float friction1) { btScalar friction = friction0 * friction1; const btScalar MAX_FRICTION = 10.f; if (friction < -MAX_FRICTION) friction = -MAX_FRICTION; if (friction > MAX_FRICTION) friction = MAX_FRICTION; return friction; } inline btScalar calculateCombinedRestitution( float restitution0, float restitution1) { return restitution0 * restitution1; } int main( int argc, char ** argv) { UserCollisionAlgorithm* userCollisionAlgorithm = new UserCollisionAlgorithm; userCollisionAlgorithm->initPhysics(); userCollisionAlgorithm->setCameraDistance(30.f); return glutmain(argc, argv,640,480, "Static Concave Mesh Demo" ,userCollisionAlgorithm); } void UserCollisionAlgorithm::initPhysics() { #define TRISIZE 5.f const int NUM_VERTS_X = 50; const int NUM_VERTS_Y = 50; const int totalVerts = NUM_VERTS_X*NUM_VERTS_Y; btVector3* gVertices = new btVector3[totalVerts]; int i; for ( i=0;i<NUM_VERTS_X;i++) { for ( int j=0;j<NUM_VERTS_Y;j++) { gVertices[i+j*NUM_VERTS_X].setValue((i-NUM_VERTS_X*0.5f)*TRIANGLE_SIZE,2.f*sinf(( float )i)*cosf(( float )j),(j-NUM_VERTS_Y*0.5f)*TRIANGLE_SIZE); } } btTriangleMesh* trimesh = new btTriangleMesh(); for ( i=0;i<NUM_VERTS_X-1;i++) { for ( int j=0;j<NUM_VERTS_Y-1;j++) { trimesh->addTriangle(gVertices[j*NUM_VERTS_X+i],gVertices[j*NUM_VERTS_X+i+1],gVertices[(j+1)*NUM_VERTS_X+i+1]); trimesh->addTriangle(gVertices[j*NUM_VERTS_X+i],gVertices[(j+1)*NUM_VERTS_X+i+1],gVertices[(j+1)*NUM_VERTS_X+i]); } } delete [] gVertices; bool useQuantizedBvhTree = true ; btCollisionShape* trimeshShape = new btBvhTriangleMeshShape(trimesh,useQuantizedBvhTree); //ConstraintSolver* solver = new btSequentialImpulseConstraintSolver; btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration(); btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration); btVector3 maxAabb(10000,10000,10000); btBroadphaseInterface* broadphase = new btAxisSweep3(-maxAabb,maxAabb); //SimpleBroadphase(); dispatcher->registerCollisionCreateFunc(GIMPACT_SHAPE_PROXYTYPE,GIMPACT_SHAPE_PROXYTYPE, new btSphereSphereCollisionAlgorithm::CreateFunc); btConstraintSolver* constraintSolver = new btSequentialImpulseConstraintSolver(); m_dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,constraintSolver,collisionConfiguration); float mass = 0.f; btTransform startTransform; startTransform.setIdentity(); startTransform.setOrigin(btVector3(0,-2,0)); btRigidBody* staticBody= localCreateRigidBody(mass, startTransform,trimeshShape); //enable custom material callback staticBody->setCollisionFlags(staticBody->getCollisionFlags() | btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK); { for ( int i=0;i<10;i++) { btCollisionShape* sphereShape = new btSphereShape(1); startTransform.setOrigin(btVector3(1,2*i,1)); //btRigidBody* body = localCreateRigidBody(1, startTransform,sphereShape); localCreateRigidBody(1, startTransform,sphereShape); } } m_dynamicsWorld->setDebugDrawer(&debugDrawer); } void UserCollisionAlgorithm::clientMoveAndDisplay() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); float dt = getDeltaTimeMicroseconds() * 0.000001f; m_dynamicsWorld->stepSimulation(dt); //optional but useful: debug drawing m_dynamicsWorld->debugDrawWorld(); renderme(); glFlush(); glutSwapBuffers(); } void UserCollisionAlgorithm::displayCallback( void ) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); renderme(); glFlush(); glutSwapBuffers(); } |
実行結果はこんな感じになります。
とても自然なシミュレーション結果です。
コンピュータ言語が苦手な方も、物理に強い理系の方であれば興味湧く分野ではないでしょうか。
どっちもダメと思っている方は、勉強を始める良い機会ととらえてください。
私が両方ダメな人間なので今回を機に勉強してみようと思います。
(しばらくユーザアルゴリズムは使わないから、あとでまたここを更新すると思います。)
追記です。最後に衝突グループの設定について説明します。
一通り Bullet について勉強して、いざ使ってみようと思ったら
お互いに衝突判定してほしくないシェイプが隣接するという問題に、いきなり直面してしまいました。
たとえば、指の関節一つ一つに円柱または四角柱のシェイプを設定したとして、ヒンジ結合で
回転角度を指定して動作させるとお互いぶつかりますよね。
こんな状況は、お互いに衝突判定を行ってほしくないです。
そこで必要になってくるのが、衝突判定を行わないシェイプ同士を設定する方法です。(グループ設定といいます。)
ではそのグループ設定というものを確認してみましょう。
1 2 3 4 5 6 7 8 9 10 | void btCollisionWorld::addCollisionObject ( btCollisionObject * collisionObject, short int collisionFilterGroup = btBroadphaseProxy::DefaultFilter, short int collisionFilterMask = btBroadphaseProxy::AllFilter ) void btDiscreteDynamicsWorld::addRigidBody ( btRigidBody * body, short group, short mask ) |
特に説明の必要はないですね…、そう登録時に第2,3引数にグループと、マスクを指定するだけなのですから。
衝突判定を行うかどうかの判定には次のビットフラグ一致判定が使われます。
1 2 | bool collides = (proxy0->m_collisionFilterGroup & proxy1->m_collisionFilterMask) != 0; collides = collides && (proxy1->m_collisionFilterGroup & proxy0->m_collisionFilterMask); |
& 演算子はお互い同じ桁のビットが立っているか調べるものです。
判定方法からわかる通り、登録時に衝突判定したくないグループのビットをマスク指定にて伏せること
こうすることで、そのビットのみ立っているグループオブジェクトに対して衝突判定を行わなくなります。
重要度、使用頻度ともに高い機能ですので、今後忘れないようにします。
ひとまず衝突判定関係のデモを一通り眺めたところで、今回の衝突判定のお話を締めくくりたいと思います。
次は、さまざまな衝突物体の導入方法について確認する予定です。
お楽しみに!
2011/08/21 初記。
2011/10/16 更新。