1. 程式人生 > >bullet物理引擎與OpenGL結合 匯入3D模型進行碰撞檢測 以及畫三角網格的坑

bullet物理引擎與OpenGL結合 匯入3D模型進行碰撞檢測 以及畫三角網格的坑

原文作者:aircraft

原文連結:https://www.cnblogs.com/DOMLX/p/11681069.html

 

 

 

 

一.初始化世界以及模型

/// 衝突配置包含記憶體的預設設定,衝突設定。高階使用者可以建立自己的配置。
btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();

/// 使用預設的衝突排程程式。對於並行處理,您可以使用不同的分派器(參見Extras/BulletMultiThreaded)
btCollisionDispatcher* dispatcher = new    btCollisionDispatcher(collisionConfiguration);

/// btDbvtBroadphase是一種很好的通用的兩步法碰撞檢測。你也可以嘗試btAxis3Sweep。
btBroadphaseInterface* overlappingPairCache = new btDbvtBroadphase();

/// 預設約束求解器。對於並行處理,您可以使用不同的解決程式(參見Extras/BulletMultiThreaded)
btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;
    
btDiscreteDynamicsWorld* dynamicsWorld =
new btDiscreteDynamicsWorld(dispatcher,overlappingPairCache,solver,collisionConfiguration);
    
dynamicsWorld->setGravity(btVector3(0,-10,0));

上面看起來很多麻煩的東西,但是其實我們都不需要看,也不需要理解,拿到dynamicsWorld這個指標就行了,它用於表示剛體碰撞的世界。後面有很多設定都是關於他的,setGravity(btVector3(0,-10,0));這個就是設定一下他的重力為10N/kg.

 

二.匯入3D模型

  在匯入3d模型前,我們要學會怎麼去畫三角網格,或者說凸殼,我在寫這個匯入3D模型碰撞檢測的程式的時候,真的是在網上找不到什麼有用的資料,頭都快裂開了!!!

  Bullet裡面有內建很多常規的3維模型畫法,比如長方體,圓,正方體之類的,並不能給我帶來什麼啟發和用處,因為匯入一個3D模型,比如OBJ檔案,就是要把一個個的三角網格畫出來,最後成為一個3D模型。

2.1三角片面碰撞模型
2.1對於複雜的碰撞模型,需要用三角片面來模擬。
靜態碰撞模型,對於大地,房屋等物體。可以用靜態的三角片面來模擬。
btBvhTriangleMeshShape 靜態的三角片面模型
構建方法
btBvhTriangleMeshShape (btStridingMeshInterface *meshInterface, bool useQuantizedAabbCompression, bool buildBvh=true)

示例程式碼
btTriangleMesh* trimesh = new btTriangleMesh();
bool useQuantization = true;
btCollisionShape* concaveShape = new btBvhTriangleMeshShape(trimesh,useQuantization); //凹的三角片面碰撞模型
startTransform.setOrigin(convexDecompositionObjectOffset);
localCreateRigidBody(0.f,startTransform,concaveShape); //質量不能設定為非0,btBvhTriangleMeshShape似乎只能用在靜態的場景中。

相關類
btTriangleMesh 一個方便的儲存三角片面資料的類,介面簡單
通過
void addTriangle (const btVector3 &vertex0, const btVector3 &vertex1, const btVector3 &vertex2, bool removeDuplicateVertices=false)
來為片面增加三角形,這個函式不會檢查相同頂點的冗餘


2.2動態的碰撞模型
btGImpactMeshShape 該類可以構建一個動態的三角片面碰撞模型
構建方法
btGImpactMeshShape (btStridingMeshInterface *meshInterface) 通過傳入三角片面資料來構建
使用該類時,一是在構建該類後要呼叫updateBound()。二是要在dispatcher中註冊該類的碰撞演算法,

示例程式碼如下:
btGImpactMeshShape * trimesh = new btGImpactMeshShape(indexVertexArrays); //構建形狀
trimesh->setLocalScaling(btVector3(4.f,4.f,4.f));
trimesh->updateBound();
m_trimeshShape = trimesh;

//register algorithm
btCollisionDispatcher * dispatcher = static_cast<btCollisionDispatcher *>(m_dynamicsWorld ->getDispatcher());
btGImpactCollisionAlgorithm::registerAlgorithm(dispatcher); //註冊演算法,如果不註冊演算法的話,會出現問題,如相同的模型不能發生碰撞


相關類
btTriangleIndexVertexArray 儲存三角片面資料
btTriangleIndexVertexArray (int numTriangles, int *triangleIndexBase, int triangleIndexStride, int numVertices, btScalar *vertexBase, int vertexStride)
通過制定三角形頂點陣列和三角形索引陣列的地址,以及每組資料大小來構建。所以類中不會實際含有片面資料。使得三角片面資料可以與渲染部分的程式碼共用。
// create trimesh
btTriangleIndexVertexArray* indexVertexArrays = new btTriangleIndexVertexArray(NUM_TRIANGLES, //片面資料
&gIndices[0][0],
3*sizeof(int),
NUM_VERTICES,(REAL*) &gVertices[0],sizeof(REAL)*3);


2.3其他類
btConvexHullShape 一個凸體模型的類,構建一個凸體。而構建這個凸體的方法十分簡單——往這個類加頂點就可以了
btConvexHullShape (const btScalar *points=0, int numPoints=0, int stride=sizeof(btVector3))
void addPoint (const btVector3 &point)
示例
btConvexHullShape* convexShape = new btConvexHullShape(); //用桌子的點集構建了一個凸的碰撞模型,雖然桌子是凹的
for (i=0;i<hull->numVertices();i++)
{
convexShape->addPoint(hull->getVertexPointer()[i]); //這個模型只需要加如點就可以了
}

 

 

最後我也是使用了btConvexHullShape,這個類來匯入3D模型。上面動態那個也可以進行碰撞檢測,我也試過了。靜態那個就不行了,因為不會動。

 

3.讀取3D模型的資料

 這裡的話就不詳細說了,可以看我前面幾篇opengl匯入3D模型的部落格。

然後我們用btConvexHullShape類將我們讀取的模型資料匯入,構造出來我們的物體。

程式碼如下:

void InitObject()
{
    

    
    ReadPIC();//讀取3D模型內部資料儲存在m_pic結構體
    btTriangleMesh* tMesh = new btTriangleMesh();
    
    int k = 0;
    for (int i = 0; i < m_pic.F.size(); i++)
    {
        points[k++].setValue(m_pic.V[m_pic.F[i].V[0]].X / YU, m_pic.V[m_pic.F[i].V[0]].Y / YU, m_pic.V[m_pic.F[i].V[0]].Z / YU);
        points[k++].setValue(m_pic.V[m_pic.F[i].V[1]].X / YU, m_pic.V[m_pic.F[i].V[1]].Y / YU, m_pic.V[m_pic.F[i].V[1]].Z / YU);
        points[k++].setValue(m_pic.V[m_pic.F[i].V[2]].X / YU, m_pic.V[m_pic.F[i].V[2]].Y / YU, m_pic.V[m_pic.F[i].V[2]].Z / YU);

    }
    btScalar mass(10.f);

    // 剛體是動態的如果且僅當質量為非零時,否則是靜止的

    btConvexHullShape * collisionShape = new btConvexHullShape((btScalar*)points, m_pic.F.size()*3);
    btDefaultMotionState* groundMotionState = new btDefaultMotionState(btTransform(btQuaternion(0, 0, 0, 1), btVector3(0, 1000, 0)));
    bool isDynamic = (mass != 0.f);

    btVector3 localInertia(0, 0, 0);
    if (isDynamic)
        collisionShape->calculateLocalInertia(mass, localInertia);
    btRigidBody::btRigidBodyConstructionInfo rbInfo(mass, groundMotionState, collisionShape, localInertia);
    body = new btRigidBody(rbInfo);
    //body->setCollisionFlags(body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT);
    //body->setActivationState(4);

    mp_btDynamicsWorld->addRigidBody(body);

    

    /*
    //init ground
    btCollisionShape *groundShape = new btBoxShape(btVector3(1000, 0.5, 1000)); //half size

    btVector3 groundpos = btVector3(0, 0, 0);
    btQuaternion groundrot(0, 0, 0, 1);
    btDefaultMotionState* groundMotion = new btDefaultMotionState(btTransform(groundrot, groundpos));
    ground = new btRigidBody(0.0, groundMotion, groundShape);//mass = 0 means it is a static object
    btScalar rest = btScalar(1);
    ground->setRestitution(rest);//設定碰撞反彈係數  預設為0
    mp_btDynamicsWorld->addRigidBody(ground);
    */
    //init ground
    btConvexHullShape *groundShape = new btConvexHullShape((btScalar*)points, m_pic.F.size() * 3);

    btVector3 groundpos = btVector3(0, 0, 0);
    
    btDefaultMotionState* groundMotion = new btDefaultMotionState(btTransform(btQuaternion(0, 0, 0, 1), btVector3(0, 0, 0)));
    ground = new btRigidBody(0.0, groundMotion, groundShape);//mass = 0 means it is a static object
    btScalar rest = btScalar(1);
    ground->setRestitution(rest);//設定碰撞反彈係數  預設為0
    mp_btDynamicsWorld->addRigidBody(ground);
    
}

 

三.進行碰撞檢測

  3.1碰撞反饋

  既然要進行碰撞檢測,那麼碰撞時,程式就要告訴我們,物體進行碰撞了,然後我們要怎麼去處理這個碰撞。。。

  那怎麼獲取碰撞時的資訊呢?

  

int numManifolds = mp_btDynamicsWorld->getDispatcher()->getNumManifolds();
    for (int i = 0; i < numManifolds; i++)
    {
        btPersistentManifold * contactManifold = mp_btDynamicsWorld->getDispatcher()->getManifoldByIndexInternal(i);
        int numContacts = contactManifold->getNumContacts();
        if (numContacts > 0)
        {
            cout << "碰撞" << endl;
        }
    }

 

我們可以獲取兩個物件的接觸點,如果存在接觸點,並且還大於0,那麼此時肯定是碰撞了,我們就可以對這個碰撞進行處理。

這個程式碼可以放在update 或者render或者display繪製裡都可以。

3.2碰撞檢測模型繪製

  這時候我們準備工作都已經做好了,就可以在自己的繪製display函式裡將3D模型繪製出來。

if (motion)delete motion;
    //motion = new btDefaultMotionState(btTransform(btQuaternion(1, 1, 0, 1), btVector3(0, 100, 0)));
    //body->setMotionState(motion);
    btTransform trans = body->getWorldTransform();

    //trans.setOrigin(btVector3(0.0f, 400, 0.0f));
    //trans.setRotation(btQuaternion(1, 1, 0, 1));
    //body->getMotionState()->setWorldTransform(trans);
    //body->getMotionState()->
    btScalar m[16];
    trans.getOpenGLMatrix(m);
    glColor3f(0, 0, 1);
    glPushMatrix();
    glMultMatrixf((GLfloat*)m);
    //glTranslated(0, -400, 0);
    
    //glutSolidCube(400);
    for (int i = 0; i < m_pic.F.size(); i++)
    {
        glBegin(GL_TRIANGLES);                            // 繪製三角形
        if (m_pic.VT.size() != 0)glTexCoord2f(m_pic.VT[m_pic.F[i].T[0]].TU, m_pic.VT[m_pic.F[i].T[0]].TV);  //紋理    
        if (m_pic.VN.size() != 0)glNormal3f(m_pic.VN[m_pic.F[i].N[0]].NX, m_pic.VN[m_pic.F[i].N[0]].NY, m_pic.VN[m_pic.F[i].N[0]].NZ);//法向量
        glVertex3f(m_pic.V[m_pic.F[i].V[0]].X / YU, m_pic.V[m_pic.F[i].V[0]].Y / YU, m_pic.V[m_pic.F[i].V[0]].Z / YU);        // 上頂點

        if (m_pic.VT.size() != 0)glTexCoord2f(m_pic.VT[m_pic.F[i].T[1]].TU, m_pic.VT[m_pic.F[i].T[1]].TV);  //紋理
        if (m_pic.VN.size() != 0)glNormal3f(m_pic.VN[m_pic.F[i].N[1]].NX, m_pic.VN[m_pic.F[i].N[1]].NY, m_pic.VN[m_pic.F[i].N[1]].NZ);//法向量
        glVertex3f(m_pic.V[m_pic.F[i].V[1]].X / YU, m_pic.V[m_pic.F[i].V[1]].Y / YU, m_pic.V[m_pic.F[i].V[1]].Z / YU);        // 左下

        if (m_pic.VT.size() != 0)glTexCoord2f(m_pic.VT[m_pic.F[i].T[2]].TU, m_pic.VT[m_pic.F[i].T[2]].TV);  //紋理
        if (m_pic.VN.size() != 0)glNormal3f(m_pic.VN[m_pic.F[i].N[2]].NX, m_pic.VN[m_pic.F[i].N[2]].NY, m_pic.VN[m_pic.F[i].N[2]].NZ);//法向量
        glVertex3f(m_pic.V[m_pic.F[i].V[2]].X / YU, m_pic.V[m_pic.F[i].V[2]].Y / YU, m_pic.V[m_pic.F[i].V[2]].Z / YU);        // 右下
        glEnd();                                        // 三角形繪製結束    
    }
    glPopMatrix();
    int numManifolds = mp_btDynamicsWorld->getDispatcher()->getNumManifolds();
    for (int i = 0; i < numManifolds; i++)
    {
        btPersistentManifold * contactManifold = mp_btDynamicsWorld->getDispatcher()->getManifoldByIndexInternal(i);
        int numContacts = contactManifold->getNumContacts();
        if (numContacts > 0)
        {
            cout << "碰撞到地面" << endl;
        }
    }
    //ground
    btTransform transg = ground->getWorldTransform();
    //trans.setOrigin(btVector3(0.0f, 400, 0.0f));
    //trans.setRotation(btQuaternion(1, 1, 0, 1));
    //body->getMotionState()->setWorldTransform(trans);
    //body->getMotionState()->
    /*
    btScalar mg[16];
    transg.getOpenGLMatrix(mg);

    glColor3f(0, 1, 0);
    glPushMatrix();
    glMultMatrixf((GLfloat*)mg);
    */
    glPushMatrix();
    glScalef(1, 0.0005, 1);
    //glScalef(1, 1, 1);
    glutSolidCube(2000); //size
   
    glPopMatrix();

 

 

四.結果

  我們可以看看這個專案的執行過程和結果圖:

 

兔子模型在進行自由落體,下面是一個地板。

 

 

 

 

 

地板顏色給我換了一下,兔子又下落了點距離。

 

 

 

 

 

 

當兔子接觸到地面時,我們將碰撞檢測的結果打印出來, 這裡也就是簡單的列印 碰撞到了地面。

 

 

 

 

可以看到我們的兔子模型,碰撞到地面之後,遵循現實物理規則,被反彈起來一點,然後砸歪了。

 

 

 

 

 

 

本專案原始碼獲得可以新增後臺小編微信傳送:本文章標題———原始碼  獲取:

  可微信掃碼關注本人公眾號,裡面會不定期的分享各種程式設計教程,和共享原始碼,諸如研究分享關於c/c++,python,前端,後端,opencv,halcon,opengl,機器學習深度學習之類有關於基礎程式設計,影象處理和機器視覺開發的知識

&n