gjk算法代码

效果

1) 可以显示gjk的每一步执行情况,步骤可以在Inspector上调

2) 步骤的信息包括:原点到最近边的垂线(蓝色线),support点用的方向(右上角的绿色箭头), support点用的shape上的点(在shape上用绿线连接)(原点到最新support点的绿色连线和这跟线是一样长度)

3) shape上红色方块标出的是shape的起始顶点

 

[Serializable]
public struct SupportPoint
{
    public Vector2 m_Point;
    public int m_ShapeAVertIndex;
    public int m_ShapeBVertIndex;
}

 

单形体 

public class Simplex
{
    private List<SupportPoint> m_PointList = new List<SupportPoint>();

    public void Add(Vector2 p, int shapeAVertIndex, int shapeBVertIndex)
    {
        var sp = new SupportPoint();
        sp.m_Point = p;
        sp.m_ShapeAVertIndex = shapeAVertIndex;
        sp.m_ShapeBVertIndex = shapeBVertIndex;

        m_PointList.Add(sp);
    }

    public Vector2 Get(int index)
    {
        return m_PointList[index].m_Point;
    }
    public SupportPoint GetSupportPoint(int index)
    {
        return m_PointList[index];
    }

    public void Insert(int index, Vector2 p, int shapeAVertIndex, int shapeBVertIndex)
    {
        var sp = new SupportPoint();
        sp.m_Point = p;
        sp.m_ShapeAVertIndex = shapeAVertIndex;
        sp.m_ShapeBVertIndex = shapeBVertIndex;

        m_PointList.Insert(index, sp);
    }

    public void RemoveAt(int index)
    {
        m_PointList.RemoveAt(index);
    }

    public void Clear()
    {
        m_PointList.Clear();
    }

    //是否包含该点
    public bool IsPointIn(Vector2 p)
    {
        if (1 == m_PointList.Count)
            return Mathf.Approximately((p - m_PointList[0].m_Point).sqrMagnitude, 0);
        else if (2 == m_PointList.Count)
            return Shape2DHelper.IsPointOnSegment(p, m_PointList[0].m_Point, m_PointList[1].m_Point);
        else if (3 == m_PointList.Count)
            return Shape2DHelper.IsPointInTriangle(p, m_PointList[0].m_Point, m_PointList[1].m_Point, m_PointList[2].m_Point);

        return false;
    }

    public int Count
    {
        get { return m_PointList.Count; }
    }
}

 

public interface Shape
{
    Vector2 GetFarthestVertInDir(Vector2 dir, out int vertIndex);
} 

 

public partial class GJK
{
    public const float Epsilon = 0.000001f; //浮点数误差

    private Simplex m_Simplex = new Simplex(); //单行体
    private int m_MaxIterCount = 20; //防止死循环的

    private List<StepCmd> m_StepCmdList; //调试用: 用于展示每一步的执行情况

    public GJK(List<StepCmd> list)
    {
        m_StepCmdList = list;
    }

    //测试是否发生碰撞
    public bool Test(Shape s1, Shape s2)
    {
        m_Simplex.Clear();
        m_StepCmdList.Clear();

        int shapeAVertIndex = 0;
        int shapeBVertIndex = 0;
        var dir = FindFirstDir();
        var supportPoint = Support(s1, s2, dir, out shapeAVertIndex, out shapeBVertIndex);
        m_Simplex.Add(supportPoint, shapeAVertIndex, shapeBVertIndex);
        AddStepCmd(dir);

        dir = -dir;
        for (int i = 0; i < m_MaxIterCount; ++i)
        {
            if (dir.sqrMagnitude < Epsilon) // 方向接近于0,说明原点就在边上
                return true;

            supportPoint = Support(s1, s2, dir, out shapeAVertIndex, out shapeBVertIndex);
            if (Vector2.Dot(supportPoint, dir) < Epsilon) //该方向上没有更远的点, 即无法跨越原点了
                return false;

            m_Simplex.Add(supportPoint, shapeAVertIndex, shapeBVertIndex);
            AddStepCmd(dir);

            if (m_Simplex.IsPointIn(Vector2.zero)) //原点是否在单形体内return true;

            dir = FindNextDir();
        }
        return false;
    }

    //一般初始方向随机取一个也不会有问题
    private Vector2 FindFirstDir()
    {
        return Vector2.right;
    }

    //找出形状1在dir方向上最远的点, 形状2在-dir方向上最远的点
    private Vector2 Support(Shape s1, Shape s2, Vector2 dir, out int shapeAVertIndex, out int shapeBVertIndex)
    {
        var a = s1.GetFarthestVertInDir(dir, out shapeAVertIndex);
        var b = s2.GetFarthestVertInDir(-dir, out shapeBVertIndex);
        //Debug.Log($"Support: dir: {dir}; {a}, {b}");
        var result = a - b;
        return result;
    }

    private Vector2 FindNextDir()
    {
        if (2 == m_Simplex.Count) //1阶单纯形: 线段
        {
            var a = m_Simplex.Get(0);
            var b = m_Simplex.Get(1);
            var abPerp = Shape2DHelper.GetPerpendicularToOrigin(a, b);

            var topStepCmd = m_StepCmdList[m_StepCmdList.Count - 1];
            topStepCmd.m_Perp = abPerp;

            return Vector2.zero - abPerp; //原点到线段的垂线
        }

        if (3 == m_Simplex.Count) //2阶单纯形: 三角形
        {
            var a = m_Simplex.Get(0);
            var b = m_Simplex.Get(1);
            var c = m_Simplex.Get(2);
            var caPerp = Shape2DHelper.GetPerpendicularToOrigin(c, a);
            var cbPerp = Shape2DHelper.GetPerpendicularToOrigin(c, b);
            //使用离原点更近的线段
            if (caPerp.sqrMagnitude < cbPerp.sqrMagnitude)
            {
                m_Simplex.RemoveAt(1);

                var topStepCmd = m_StepCmdList[m_StepCmdList.Count - 1];
                topStepCmd.m_Perp = caPerp;

                return Vector2.zero - caPerp; //原点到线段的垂线
            }
            else
            {
                m_Simplex.RemoveAt(0);

                var topStepCmd = m_StepCmdList[m_StepCmdList.Count - 1];
                topStepCmd.m_Perp = cbPerp;

                return Vector2.zero - cbPerp; //原点到线段的垂线
            }
        }
        //error: 不可能执行到这边
        return Vector2.zero;
    }

    private void AddStepCmd(Vector2 dir)
    {
        var cmd = new StepCmd();

        cmd.m_Dir = dir;
        cmd.m_SimplexPointList = new List<SupportPoint>();

        for (int i = 0; i < m_Simplex.Count; ++i)
        {
            var sp = m_Simplex.GetSupportPoint(i);
            cmd.m_SimplexPointList.Add(sp);
        }

        m_StepCmdList.Add(cmd);
    }

}

 

public class GJKPolygon : MonoBehaviour, Shape
{
    [SerializeField]
    private List<Transform> m_VertList = new List<Transform>();

    public Vector2 GetVert(int index)
    {
        var trans = m_VertList[index];
        return trans.position;
    }

    public int GetVertCount()
    {
        return m_VertList.Count;
    }

    public Vector2 GetFarthestVertInDir(Vector2 dir, out int vertIndex)
    {
        vertIndex = 0;
        Vector2 maxVert = GetVert(0);
        float maxDist = Vector2.Dot(maxVert, dir);

        for (int i = 1; i < m_VertList.Count; ++i)
        {
            var vert = GetVert(i);
            float proj = Vector2.Dot(vert, dir);
            if (proj > maxDist)
            {
                maxDist = proj;
                maxVert = vert;
                vertIndex = i;
            }
        }
        return maxVert;
    }

#if UNITY_EDITOR

    [NonSerialized]
    public bool m_IsHighlight = false;
    private Vector3 m_PointSize = Vector3.zero;

    private void OnDrawGizmos()
    {
        if (null == m_VertList || m_VertList.Count < 3)
            return;

        for (var i = 0; i < m_VertList.Count; ++i)
        {
            if (null == m_VertList[i]) return;
        }

        if (m_IsHighlight)
            Gizmos.color = Color.red;

        for (var i = 0; i < m_VertList.Count; ++i)
        {
            var a = GetVert(i);
            var b = GetVert((i+1) % m_VertList.Count);
            if (0 == i)
                DrawPoint(a, ref m_PointSize); //第1个点

            Gizmos.DrawLine(a, b);
        }

        Gizmos.color = Color.white;
    }

    public static void DrawPoint(Vector2 point, ref Vector3 pointSize)
    {
        float handleSize = HandleUtility.GetHandleSize(point) * 0.1f;
        pointSize.Set(handleSize, handleSize, 0.01f);
        Gizmos.DrawCube(point, pointSize);
    }

#endif

}

 

//显示调试图形所需的数据
[Serializable]
public class StepCmd 
{
    public Vector2 m_Dir; //这次找Support点, 取的方向
    public List<SupportPoint> m_SimplexPointList; //这一步迭代时的Support点copy
    public Vector2 m_Perp; //原点到单形体边的垂足
}

 

测试代码

public class GJKTest : MonoBehaviour
{
    public GJKPolygon m_Polygon1;
    public GJKPolygon m_Polygon2;

    private GJK m_GJK;

    [Range(0, 20)]
    public int m_Step = 0;
    private List<GJK.StepCmd> m_StepCmdList = new List<GJK.StepCmd>();
    public GJK.StepCmd m_CurStepCmd;

    private Vector3 m_CubeSize = new Vector3(0.02f, 0.02f, 0.01f);

    void Start()
    {
        m_GJK = new GJK(m_StepCmdList);
    }

    void Update()
    {
        if (m_Polygon1 && m_Polygon2)
        {
            var result = m_GJK.Test(m_Polygon1, m_Polygon2);
            m_Polygon1.m_IsHighlight = result;
            m_Polygon2.m_IsHighlight = result;
        }
    }


#if UNITY_EDITOR
    public Vector2 m_DirStartPos = new Vector2(3, 3);

    private void OnDrawGizmos()
    {
        m_CurStepCmd = null;
        if (m_StepCmdList.Count > 0)
        {
            m_Step = Mathf.Min(m_StepCmdList.Count - 1, Mathf.Max(m_Step, 0));
            var stepCmd = m_StepCmdList[m_Step];
            m_CurStepCmd = stepCmd;

            var lastSupportPoint = new GJK.SupportPoint();
            if (1 == stepCmd.m_SimplexPointList.Count)
            {
                var p1 = stepCmd.m_SimplexPointList[0];
                DrawPoint(p1.m_Point, ref m_CubeSize);

                Gizmos.color = Color.green;
                lastSupportPoint = p1;
                Gizmos.color = Color.blue;
            }
            else if (2 == stepCmd.m_SimplexPointList.Count)
            {
                var p1 = stepCmd.m_SimplexPointList[0];
                var p2 = stepCmd.m_SimplexPointList[1];
                Gizmos.DrawLine(p1.m_Point, p2.m_Point);

                Gizmos.color = Color.blue;
                lastSupportPoint = p2;
                Gizmos.DrawLine(stepCmd.m_Perp, Vector2.zero); //原点到最近边的垂线
            }
            else
            {
                for (int i = 0; i < stepCmd.m_SimplexPointList.Count; ++i)
                {
                    var p1 = stepCmd.m_SimplexPointList[i];
                    var p2 = stepCmd.m_SimplexPointList[(i + 1) % stepCmd.m_SimplexPointList.Count];
                    lastSupportPoint = p1;
                    Gizmos.DrawLine(p1.m_Point, p2.m_Point);
                }

                Gizmos.color = Color.blue;
                Gizmos.DrawLine(stepCmd.m_Perp, Vector2.zero); //原点到最近边的垂线
            }

            Gizmos.color = Color.green;
            Gizmos.DrawLine(lastSupportPoint.m_Point, Vector2.zero);

            //构成support点的shape上的点
            var shapeAVert = m_Polygon1.GetVert(lastSupportPoint.m_ShapeAVertIndex);
            var shapeBVert = m_Polygon2.GetVert(lastSupportPoint.m_ShapeBVertIndex);
            Gizmos.DrawLine(shapeAVert, shapeBVert);

            //寻找support点时用的方向
            Gizmos.color = Color.green;
            DrawArrow(m_DirStartPos, stepCmd.m_Dir.normalized, 0.8f);

            Gizmos.color = Color.white;
        }
    }

    public static void DrawPoint(Vector2 point, ref Vector3 pointCubeSize)
    {
        float handleSize = HandleUtility.GetHandleSize(point) * 0.1f;
        pointCubeSize.Set(handleSize, handleSize, 0.01f);
        Gizmos.DrawCube(point, pointCubeSize);
    }
    public static void DrawArrow(Vector2 start, Vector2 dir, float len, float size = 0.2f)
    {
        var end = start + dir * len;
        Gizmos.DrawLine(start, end);
        var arrowSize = new Vector2(size, 0);

        Vector2 arrowLineDirCCW = Quaternion.Euler(0, 0, 150) * dir * 0.2f;
        Gizmos.DrawLine(end, end + arrowLineDirCCW);
        Vector2 arrowLineDirCW = Quaternion.Euler(0, 0, -150) * dir * 0.2f;
        Gizmos.DrawLine(end, end + arrowLineDirCW);
    }

#endif

}

 

参考

碰撞检测——GJK算法_码银的博客-CSDN博客

碰撞检测算法之GJK算法_满腹的小不甘的博客-CSDN博客

碰撞检测算法之GJK算法 - 知乎 (zhihu.com)

物理引擎学习03-GJK碰撞检测算法基础_gjk算法-CSDN博客

2D凸多边形碰撞检测算法(二) - GJK(上) - 知乎 (zhihu.com)

2D凸多边形碰撞检测算法(二) - GJK(下) - 知乎 (zhihu.com)

 

posted @ 2023-11-14 23:03  yanghui01  阅读(50)  评论(0编辑  收藏  举报