3D碰撞分辨率,移动AABB +多面体

时间:2017-01-16 06:25:07

标签: c++ algorithm 3d collision-detection

我一直在空闲时间写一个游戏引擎,但是我已经被困了几个星期试图让碰撞工作。

目前,我正在使用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());
}

我一直在通过“在玩家移动方向上的碰撞距离”对碰撞面进行排序,但我还没有找到一种有效的方法来找到所有面部的距离值。

有没有人知道一种能够更好地完成我想要完成的算法?

1 个答案:

答案 0 :(得分:0)

我对代码的第一部分非常怀疑。你在每次迭代中修改实体的位置,对吗?这或许可以解释奇怪的边缘情况。

在2D示例中,如果正方形朝向锐角并且与两个墙壁碰撞,则其位置将首先被一个墙壁修改,这使其更多地穿透到第二个墙壁中。然后第二个墙使用更大的比例值改变其位置,这样看起来方形只被一面墙推动。

如果在表面S的法线接近玩家的移动时发生碰撞,则将比其他所有碰撞处理得晚。请注意,在处理其他碰撞时,玩家的位置会被修改,并且可能会更多地渗透到表面S中。所以最后程序会处理与表面S的碰撞,这会让玩家大量弹出。

我认为有一个简单的解决方法。只需计算一次穿透,然后使用时间变量对所有位移求和,然后用总位移改变位置。