2017-01-16 54 views
1

我一直在空閒時間寫一個遊戲引擎,但是我一直堅持幾個星期試圖讓碰撞工作。3D碰撞分辨率,移動AABB +多面體

目前,我用AABB表示實體的碰撞體,而一個關卡的碰撞體由一個相當簡單的(但不一定是凸的)多面體表示。所有圖形都是基於精靈的,但底層的碰撞代碼是3D。我已經使用this算法(我可以天真地應用於關卡網格中的每個面孔)運行AABB /三角形碰撞檢測,但是我在檢測到它存在後試圖解決碰撞問題時卡住了。

我提出的算法工作得很好,但有一些邊緣情況下會中斷。例如,直接走向尖銳的角落總是會將玩家推向一側或另一側。或者,如果一個小碰撞面碰巧具有比所有其他面部更接近玩家運動方向的法線,則會首先在該方向上「彈出」該玩家,即使使用來自不同面部的偏移將會具有效果更好。

僅供參考,我目前的算法是這樣的:

Create list of all colliding faces 
Sort list in increasing order of the angle between face normal and negative direction of entity movement (i.e. process faces with the most "stopping power" first) 
For each colliding face in collision list: 
    scale = distance of collision along face normal 
    Entity position += face normal * scale 
    If no more collision: 
     break 

而這裏的實現:

void Mesh::handleCollisions(Player& player) const 
{ 
    using Face = Face<int32_t>; 
    BoundingBox<float> playerBounds = player.getGlobalBounds(); 
    Vector3f negPlayerDelta = -player.getDeltaPos(); // Negative because face norm should be opposite direction of player dir 

    auto comparator = [&negPlayerDelta](const Face& face1, const Face& face2) { 
     const Vector3f norm1 = face1.normal(); 
     const Vector3f norm2 = face2.normal(); 
     float closeness1 = negPlayerDelta.dot(norm1)/(negPlayerDelta.magnitude() * norm1.magnitude()); 
     float closeness2 = negPlayerDelta.dot(norm2)/(negPlayerDelta.magnitude() * norm2.magnitude()); 
     return closeness1 > closeness2; 
    }; 

    std::vector<Face> collidingFaces; 
    for (const Face& face : _faces) 
    { 
     ::Face<float> floatFace(face); 
     if (CollisionHelper::collisionBetween(playerBounds, floatFace)) 
     { 
      collidingFaces.push_back(face); 
     } 
    } 
    if (collidingFaces.empty()) { 
     return; 
    } 
    // Process in order of "closeness" between player delta and face normal 
    std::sort(collidingFaces.begin(), collidingFaces.end(), comparator); 

    Vector3f totalOffset; 
    for (const Face& face : collidingFaces) 
    { 
     const Vector3f& norm = face.normal().normalized(); 
     Point3<float> closestVert(playerBounds.xMin, playerBounds.yMin, playerBounds.zMin); // Point on AABB that is most negative in direction of norm 
     if (norm.x < 0) 
     { 
      closestVert.x = playerBounds.xMax; 
     } 
     if (norm.y < 0) 
     { 
      closestVert.y = playerBounds.yMax; 
     } 
     if (norm.z < 0) 
     { 
      closestVert.z = playerBounds.zMax; 
     } 
     float collisionDist = closestVert.vectorTo(face[0]).dot(norm); // Distance from closest vert to face 
     Vector3f offset = norm * collisionDist; 
     BoundingBox<float> newBounds(playerBounds + offset); 
     totalOffset += offset; 
     if (std::none_of(collidingFaces.begin(), collidingFaces.end(), 
         [&newBounds](const Face& face) { 
          ::Face<float> floatFace(face); 
          return CollisionHelper::collisionBetween(newBounds, floatFace); 
         })) 
     { 
      // No more collision; we are done 
      break; 
     } 
    } 
    player.move(totalOffset); 
    Vector3f playerDelta = player.getDeltaPos(); 
    player.setVelocity(player.getDeltaPos()); 
} 

我一直在搞亂播放器的方向由「碰撞距離排序的碰撞面運動「,但我還沒有找到找到所有人臉距離值的有效方法。

有沒有人知道一個算法,會更好地爲我所要完成的工作?

+0

你目前的代碼與你的問題有什麼關係? – xaxxon

+0

主要供參考/上下文。但我認爲我目前的算法非常接近正確;它可能只需要一些小修改來修復邊緣情況。 – 0x5453

回答

0

我對代碼的第一部分頗感懷疑。你在每次迭代中修改實體的位置,對嗎?這可能能夠解釋奇怪的邊緣情況。

在一個2D例子中,如果一個正方形走向一個尖角並與兩個牆壁碰撞,它的位置將首先被一個牆壁修改,這使得它更多地進入第二牆壁。然後第二面牆使用更大的比例值改變其位置,以便看起來正方形只被一面牆推開。

如果碰撞發生在表面S的法線靠近玩家移動的地方,那麼碰撞將比所有其他碰撞晚。請注意,在處理其他碰撞時,玩家的位置會被修改,並且很可能會滲透到表面S中。因此,最終該程序會處理與表面S的碰撞,從而使玩家大受其害。

我認爲有一個簡單的修復。只需立即計算穿透深度,並使用時間變量來總和所有位移,然後用總位移來改變位置。