Обнаружение столкновений OBB неправильно определяет вектор перевода - PullRequest
0 голосов
/ 02 марта 2020

РЕДАКТИРОВАТЬ: Исправлено благодаря stackoverflow.com Некоторые случаи отсутствовали в CalculateOverlap (). Добавил их, чтобы вы могли их видеть.


У меня есть класс Hitbox C# (см. Код ниже, особенно метод CollisionTest () ), который использует SAT для обнаружения столкновения между двумя OBB. Это прекрасно работает для следующего случая ...

Working collision detection example

... но полностью не работает в следующем случае:

enter image description here

Расчетный вектор минимального перемещения слишком велик, в результате чего объект подпрыгивает, а не просто приспосабливается к сталкивающемуся объекту.

Что делать Мне нужно сделать, чтобы улучшить мой алгоритм? Любая помощь с благодарностью!

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

1 Ответ

1 голос
/ 03 марта 2020

Кажется, что где-то (в CalculateOverlap) вы проверяете, находится ли нижняя граница A внутри B. Вы пропустили случай, когда нижняя граница A находится под нижней границей B, а верхняя граница A находится выше верхней границы B.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...