OBB 碰撞检测错误地检测到平移矢量

OBB collision detection detecting translation vector incorrectly

编辑:由于 whosebug.com 已修复 CalculateOverlap() 中缺少一些案例。添加给你看。


我有一个 C# Hitbox class(参见下面的代码,尤其是 CollisionTest() 方法),它使用 SAT 检测两个 OBB 之间的碰撞。这对于以下情况非常有效...

...但在以下情况下完全失败:

计算出的最小平移向量太高,导致对象跳起来而不是仅仅适应碰撞对象。

我需要做什么来改进我的算法?非常感谢任何帮助!

using OpenTK;

namespace KWEngine2.Collision3D
{
    public class Hitbox
    {
        private static Vector3[] STATICVERTICES = new Vector3[]
        { 
            new Vector3(-0.5f, -0.5f, +0.5f),
            new Vector3(+0.5f, -0.5f, +0.5f),
            new Vector3(+0.5f, -0.5f, -0.5f),
            new Vector3(-0.5f, -0.5f, -0.5f),

            new Vector3(+0.5f, +0.5f, +0.5f),
            new Vector3(-0.5f, +0.5f, +0.5f),
            new Vector3(-0.5f, +0.5f, -0.5f),
            new Vector3(+0.5f, +0.5f, -0.5f)
        };

        private static Vector3[] STATICNORMALS = new Vector3[]
        {
            new Vector3(1, 0, 0),
            new Vector3(0, 1, 0),
            new Vector3(0, 0, 1)
        };

        private static Vector3 STATICCENTER = new Vector3(0, 0, 0);

        // Holds the currently transformed vertices of the bounding box
        private Vector3[] _vertices = new Vector3[8];

        // Holds the currently transformed surface normals of the bounding box
        private Vector3[] _normals = new Vector3[3];

        // Holds the currently transformed center point of the bounding box
        private Vector3 _center = new Vector3(0, 0, 0);


        private static Matrix4 CreateModelMatrix(Vector3 position, Quaternion rotation, Vector3 scale)
        {
            // Create the basic 4x4 rotation matrix:
            Matrix4 m = Matrix4.CreateFromQuaternion(rotation);

            // Multiply the first three rows by the scale to add the given scale:
            m.Row0 *= scale.X;
            m.Row1 *= scale.Y;
            m.Row2 *= scale.Z;

            // Replace the lowest row with the position data:
            m.Row3.X = position.X;
            m.Row3.Y = position.Y;
            m.Row3.Z = position.Z;
            m.Row3.W = 1.0f;

            return m;
        }


        public Hitbox()
        {
            Update(Vector3.Zero, Quaternion.Identity, Vector3.One);
        }

        public Hitbox(Vector3 position, Quaternion rotation, Vector3 scale)
        {
            Update(position, rotation, scale);
        }

        public void Update(Vector3 position, Quaternion rotation, Vector3 scale)
        {
            Matrix4 modelMatrix = CreateModelMatrix(position, rotation, scale);

            for (int i = 0; i < 8; i++)
            {
                if (i < 3)
                {
                    Vector3.TransformNormal(ref STATICNORMALS[i], ref modelMatrix, out _normals[i]);
                }
                // transform the corner points by multiplying them by the model matrix:
                Vector3.TransformPosition(ref STATICVERTICES[i], ref modelMatrix, out _vertices[i]);
            }
            // transform the center point by multiplying it by the model matrix:
            Vector3.TransformPosition(ref STATICCENTER, ref modelMatrix, out _center);
        }

        public static bool CollisionTest(Hitbox a, Hitbox b, out Vector3 MTV)
        {

            float mtvDistance = float.MaxValue;
            float mtvDirection = 1;
            MTV = Vector3.Zero;

            for (int i = 0; i < 3; i++)
            {
                bool error;
                float shape1Min, shape1Max, shape2Min, shape2Max;
                SatTest(ref a._normals[i], ref a._vertices, out shape1Min, out shape1Max);
                SatTest(ref a._normals[i], ref b._vertices, out shape2Min, out shape2Max);
                if (!Overlaps(shape1Min, shape1Max, shape2Min, shape2Max))
                {
                    return false;
                }
                else
                {
                    CalculateOverlap(ref a._normals[i], ref shape1Min, ref shape1Max, ref shape2Min, ref shape2Max,
                        out error, ref mtvDistance, ref MTV, ref mtvDirection, ref a._center, ref b._center);
                    if (error)
                        return false;
                }


                SatTest(ref b._normals[i], ref a._vertices, out shape1Min, out shape1Max);
                SatTest(ref b._normals[i], ref b._vertices, out shape2Min, out shape2Max);
                if (!Overlaps(shape1Min, shape1Max, shape2Min, shape2Max))
                {
                    return false;
                }
                else
                {
                    CalculateOverlap(ref b._normals[i], ref shape1Min, ref shape1Max, ref shape2Min, ref shape2Max,
                        out error, ref mtvDistance, ref MTV, ref mtvDirection, ref a._center, ref b._center);
                    if (error)
                        return false;
                }

            }
            return true;
        }

        private static void SatTest(ref Vector3 axisToTest, ref Vector3[] points, out float minAlong, out float maxAlong)
        {
            minAlong = float.MaxValue;
            maxAlong = float.MinValue;
            for (int i = 0; i < points.Length; i++)
            {
                float dotVal = Vector3.Dot(points[i], axisToTest);
                if (dotVal < minAlong) minAlong = dotVal;
                if (dotVal > maxAlong) maxAlong = dotVal;
            }
        }

        private static bool Overlaps(float min1, float max1, float min2, float max2)
        {
            return IsBetweenOrdered(min2, min1, max1) || IsBetweenOrdered(min1, min2, max2);
        }

        private static bool IsBetweenOrdered(float val, float lowerBound, float upperBound)
        {
            return lowerBound <= val && val <= upperBound;
        }

        private static void CalculateOverlap(ref 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, ref Vector3 posA, ref Vector3 posB)
        {
            // THIS PART IS NEW!
            float intersectionDepthScaled;
            if (shape1Min < shape2Min)
            {
                if (shape1Max > shape2Max)
                {
                    float diff1 = shape1Max - shape2Max;
                    float diff2 = shape2Min - shape1Min;
                    if(diff1 > diff2)
                    {
                        intersectionDepthScaled = shape2Max - shape1Min;
                    }
                    else
                    {
                        intersectionDepthScaled = shape2Min - shape1Max;
                    }

                }
                else
                {
                    intersectionDepthScaled = shape1Max - shape2Min; // default
                }

            }
            else
            {
                if(shape1Max < shape2Max)
                {
                    float diff1 = shape2Max - shape1Max;
                    float diff2 = shape1Min - shape2Min;
                    if (diff1 > diff2)
                    {
                        intersectionDepthScaled = shape1Max - shape2Min;
                    }
                    else
                    {
                        intersectionDepthScaled = shape1Min - shape2Max;
                    }
                }
                else
                {
                    intersectionDepthScaled = shape1Min - shape2Max; // default
                }

            }
            // END OF NEW PART

            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;
            }
        }
    }
}

似乎在某处(进入 CalculateOverlap)检查 A 的下限是否在 B 内。您错过了 A 的下限低于 B 的下限且 A 的上限高于 B 的上限的情况.