【问题标题】:Getting OBB-Triangle collision detection right in OpenGL engine在 OpenGL 引擎中正确获取 OBB-Triangle 碰撞检测
【发布时间】:2019-07-04 02:26:51
【问题描述】:

我想实现地形碰撞。我有一个名为 GeoTerrain 的课程。它的构造函数将图片作为参数,然后用三角形构建地形,每个亮像素的顶点高,每个暗像素的顶点低。 这使我处于一个位置,当 OBB 撞到地形时,我必须对它们实施适当的碰撞检测。但我的临时解决方案远非稳健。

这是我目前的方法: 每个表面三角形都有自己的小碰撞箱,由 6 个顶点组成(三角形表面上的 3 个顶点和三个额外的顶点,它们基本上是 3 个表面顶点,但沿表面法线向下移动(因此它们位于可见表面下方)。

为什么:因为我不知道如何在没有 hitbox 体积的情况下计算最小平移向量。

抱歉,代码太长了 - 我尽可能多地发表评论:

// This method resides in the Hitbox class.
// The hitbox instance then checks against the other object (parameter):
private IntersectionObject TestIntersectionTerrain(GeoTerrain terra)
{
    Vector3 mtv = new Vector3(0, 0, 0);
    Vector3 mtvTotal = new Vector3(0, 0, 0);
    float shape1Min, shape1Max, shape2Min, shape2Max;

    // Select all triangles within reach of the OBB hitbox:
    List<GeoTriangle> triangles = terra.GetTrianglesForHitbox(this);

    // Loop through all triangles and check collision
    // (cannot be more than 8 triangles, right now)
    foreach (GeoTriangle triangle in triangles)
    {
        bool bothOverlap = false;
        mtv = Vector3.Zero;
        bool error;
        bool breakDone = false;
        float mtvDistance = float.MaxValue;
        float mtvDirection = 1;

        // loop through all hitbox normals (three normals):
        for (int i = 0; i < mNormals.Length; i++)
        {
            error = false;

            // project the current vertices of objects' hitbox and triangle hitbox to find
            // both shapes' minimum and maximum:
            SATtest(CurrentHitBoxNormals[i], CurrentHitBoxVertices, out shape1Min, out shape1Max);
            SATtest(CurrentHitBoxNormals[i], triangle.VerticesHitbox, out shape2Min, out shape2Max);
            if (!Overlaps(shape1Min, shape1Max, shape2Min, shape2Max))
            {
                bothOverlap = false;
                breakDone = true;
                break;
            }
            else
            {
                // calculate MTV: 
                CalculateOverlap(
                    CurrentHitBoxNormals[i], // normals (here, the meshes' hitbox normals)
                    ref shape1Min,
                    ref shape1Max,
                    ref shape2Min,
                    ref shape2Max, 
                    out error,
                    ref mtvDistance,
                    ref mtv,
                    ref mtvDirection,
                    mCenterTranslated, // object's hitbox volume center (world space)
                    triangle.CenterHitbox); // triangle's hitbox volume center (world space)
            }

            // do the same but now for the triangle's normal
            // (right now this unnecessarily also gets looped 3 times):
            SATtest(triangle.Normal, CurrentHitBoxVertices, out shape1Min, out shape1Max);
            SATtest(triangle.Normal, triangle.VerticesHitbox, out shape2Min, out shape2Max);
            if (!Overlaps(shape1Min, shape1Max, shape2Min, shape2Max))
            {
                bothOverlap = false;
                breakDone = true;
            }
            else
            {
                CalculateOverlap(triangle.Normal, ref shape1Min, ref shape1Max, ref shape2Min, ref shape2Max,
                    out error, ref mtvDistance, ref mtv, ref mtvDirection, mCenterTranslated, triangle.CenterHitbox);
                bothOverlap = true;
            }
        }
        if (bothOverlap && !breakDone && mtv != Vector3.Zero)
        {
            // add the current mtv to the total MTV (of all triangles)
            // but only add more to it, if the current MTV has a bigger shift in
            // one direction than the previous MTVs:
            mtvTotal.X = Math.Abs(mtv.X) > Math.Abs(mtvTotal.X) ? mtv.X : mtvTotal.X;
            mtvTotal.Y = Math.Abs(mtv.Y) > Math.Abs(mtvTotal.Y) ? mtv.Y : mtvTotal.Y;
            mtvTotal.Z = Math.Abs(mtv.Z) > Math.Abs(mtvTotal.Z) ? mtv.Z : mtvTotal.Z;
        }

    }

    if (mtvTotal != Vector3.Zero)
    {
        IntersectionObject o = new IntersectionObject();
        o.IntersectingGameObject = terra.Owner;
        o.MinimumTranslationVector = mtvTotal;
        o.MeshNumber = 0;
        o.MeshName = terra.Owner.Name;

        return o;
    }
    else
    {
        return null;
    }
}

private void CalculateOverlap(Vector3 axis, ref float shape1Min, ref float shape1Max, ref float shape2Min, ref float shape2Max, out bool error, ref float mtvDistance, ref Vector3 mtv, ref float mtvDirection, Vector3 posA, Vector3 posB)
{
    float d0 = (shape2Max - shape1Min); 
    float d1 = (shape1Max - shape2Min);
    float intersectionDepthScaled = (shape1Min < shape2Min)? (shape1Max - shape2Min) : (shape1Min - shape2Max);

    float axisLengthSquared = Vector3.Dot(axis, axis);
    if (axisLengthSquared < 1.0e-8f)
    {
        error = true;
        return;
    }
    float intersectionDepthSquared = (intersectionDepthScaled * intersectionDepthScaled) / axisLengthSquared;

    error = false;

    if (intersectionDepthSquared < mtvDistance || mtvDistance < 0)
    {
        mtvDistance = intersectionDepthSquared;
        mtv = axis * (intersectionDepthScaled / axisLengthSquared);
        float notSameDirection = Vector3.Dot(posA - posB, mtv);
        mtvDirection =  notSameDirection < 0 ? -1.0f : 1.0f;
        mtv = mtv * mtvDirection;
    }

}

问题是:它有效,但不在边缘。使用我的方法(三角形表面下方的隐形碰撞箱),玩家有时会碰到一堵隐形墙,因为 MTV 告诉他向上移动(好的)和侧向移动(不好)。从数学上讲这是正确的,因为如果玩家陷入假三角形体积中,mtv 当然可能会在需要时决定将他移回多个轴上。

有什么方法可以让 MTV 仅通过检查一个扁平三角形来获得?我发现几个链接告诉我“如果”有一个交叉点。但我没有找到可以理解的获取 MTV 的解决方案。我想我需要找出玩家必须沿着表面法线向后移动多少?


编辑(2019-02-12 22:00)

我根据第一个答案更新了代码。但是在应用计算的 MTV 时,我仍然会出现奇怪的行为。这是我目前的算法:

到目前为止,我的更新碰撞检测算法方法:

  1. 获取靠近玩家的三角形列表
  2. 获取 OBB 顶点和法线
  3. 循环遍历该三角形列表并为每个三角形执行(现在最多约 8 个):
  4. 使用三角形的法线和投影三角形的顶点和 OBB 的顶点。
  5. 使用 OBB 的法线 #1 并投影三角形的顶点和 OBB 的顶点。
  6. 使用 OBB 的法线 #2 并投影三角形的顶点和 OBB 的顶点。
  7. 使用 OBB 的法线 #3 并投影三角形的顶点和 OBB 的顶点。
  8. 交叉您告诉我的向量(第 5 步到第 13 步)并将三角形的顶点和 OBB 的顶点投影到这些向量的标准化版本上
  9. 只有在上一步导致重叠时才会执行每个步骤。然后将重叠长度存储在一个数组中(13 个单元,每个步骤一个)
  10. 如果每个投影都导致重叠,请通过计算最小重叠和相应的轴矢量来查找 mtv。
  11. 将此向量乘以重叠并将其用作 MTV。

这样说对吗?

【问题讨论】:

    标签: c# collision-detection mesh bounding-box


    【解决方案1】:

    SAT(分离轴定理)算法很容易为您提供最小平移向量,适用于任何类型的凸形,也可以包括三角形、四边形、平面多边形。

    以蛮力的方式,SAT 分两步进行,多两步才能获得 MTV。

    • 找到要测试的最小分离轴列表。

      • 例如,对于 OBB,您需要考虑三个方向 面和边的三个方向。
    • 在分离轴上投影两个凸形,得到投影间隔。

      • 例如,对于每个形状,将其所有顶点与轴进行点积 方向。

      • 最小值和最大值将为您提供每个形状的间隔。

    • 如果两个区间不相交,则物体是分开的,不相交。

    • 如果所有间隔在每个spearation 轴上相交,则对象相交。

    • 要找到 MTV,找到所有操作轴间隔之间的最小重叠。

    • 间隔重叠,结合分离轴方向,将为您提供 MTV。

    对于凸多面体,要测试的分离轴非常简单。他们是:

    • 每个形状的表面法线,称它们为 A 和 B。

    • A 的每条边与 B 的每条边的叉积。

    • 有优化,例如,不需要用 相反的方向,或相似的方向。

    • 例如,OBB 需要针对三个面法线和三个边缘方向进行测试。

    注意:MTV 可能并不总是解决您的问题的最佳方法,尤其是当我们谈论地形时。

    【讨论】:

    • 感谢您的快速回复。我更新了我的问题(参见“编辑”部分)并在那里发布了我更新的算法。你认为应该这样做吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多