【问题标题】:3D Collision resolution, moving AABB + polyhedron3D碰撞分辨率,移动AABB+多面体
【发布时间】:2017-01-16 06:25:07
【问题描述】:

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

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

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

有没有人知道一种算法可以更好地完成我想要完成的任务?

【问题讨论】:

  • 您当前的代码与您的问题有什么关系?
  • 主要用于参考/上下文。但我确实认为我目前的算法非常接近正确;修复边缘情况可能只需要一些小的修改。

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


【解决方案1】:

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

在 2D 示例中,如果一个正方形走向一个尖角并与两堵墙发生碰撞,它的位置将首先被一堵墙修改,从而使其更多地穿透到第二堵墙。然后第二面墙使用更大的比例值改变了它的位置,所以看起来正方形似乎只被一堵墙推动。

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

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

【讨论】:

    猜你喜欢
    • 2019-08-06
    • 2014-05-19
    • 1970-01-01
    • 2019-10-14
    • 2011-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-05
    相关资源
    最近更新 更多