首页 > 其他分享 >四叉树加速碰撞检测

四叉树加速碰撞检测

时间:2023-11-29 23:48:26浏览次数:40  
标签:int private public 碰撞检测 shape var new 四叉树 加速

1) 加速原理:排除掉那些不可能发生的碰撞检测,通过减少碰撞检测次数来加速。

2) 如何排除不可能发生的碰撞检测?

就是将一块大区域分割成四个更小的区域,那当只可能第1个区域发生碰撞时,其余3个区域的里的物体就可以排除掉不参与碰撞检测了。

比如,待检测的物体在左上的区域时,那我只需要检测是否和左上那个小的区域中的物体发生碰撞。

 

 

2) 什么时候将大区域分为四个小区域?

这个是可以按需求规定的,比如可以设定一块区域内最多可以容纳4个物体,那当第5个物体添加进来时,

就要将要容纳了5个物体的区域一分为四,这样每个小区域内的物体数量就减少了。

 

该树的特性

1) 每个节点有4个子节点

2) 每个节点都会保存他所对应的区域,包括叶子节点

3) 只有叶子节点保存了实际参与碰撞的物体

4) 那些跨区域的物体会保存在多个叶子节点中,这个没影响获取的时候会去重

 

效果

 

using System.Collections.Generic;
using UnityEngine;

public class MyQuadTree
{
    public interface IShape
    {
        Rect GetBounds();
        string GetShapeName();
    }

    private Rect m_Bounds = new Rect(0, 0, 1, 1);
    private int m_Depth; //节点深度(从0开始)
    private int m_MaxDepth = 4; //树最大深度
    private MyQuadTree[] m_Children;// = new MyQuadTree[4]; //当前节点的4个子节点

    private List<IShape> m_ShapeList = new List<IShape>();
    private int m_SplitThreshold = 10; //存放的对象超过这个值时分裂为4个子节点

    public MyQuadTree(Rect bounds, int splitThreshold = 10, int maxDepth = 4)
    {
        m_Bounds = bounds;
        m_SplitThreshold = splitThreshold;
        m_MaxDepth = maxDepth;
    }

    public Rect Bounds
    {
        get { return m_Bounds; }
    }

    public int Depth
    {
        get { return m_Depth; }
    }

    public int MaxDepth
    {
        get { return m_MaxDepth; }
    }

    public int GetShapeCount()
    {
        return m_ShapeList.Count;
    }

    public IShape GetShape(int i)
    {
        return m_ShapeList[i];
    }

    public MyQuadTree GetChild(int index)
    {
        if (null != m_Children)
            return m_Children[index];
        return null;
    }

    public void AddShape(IShape shape)
    {
        if (null != m_Children)
        {
            AddShapeToBelong(shape);
            return;
        }

        m_ShapeList.Add(shape);
        if (m_ShapeList.Count > m_SplitThreshold)
        {
            if (m_Depth >= m_MaxDepth)
            {
                //Debug.LogWarning($"split fail: reach maxDepth:{m_MaxDepth}");
                return;
            }
            m_Children = new MyQuadTree[4];
            Split();

            //形状重新分配到新分裂的节点中
            for (int i = 0; i < m_ShapeList.Count; ++i)
            {
                AddShapeToBelong(m_ShapeList[i]);
            }
            m_ShapeList.Clear();
        }
    }

    //节点一分为4, 每个节点占据的大小为原来的1/4
    private void Split()
    {
        int nextDepth = m_Depth + 1;
        float halfWidth = m_Bounds.width * 0.5f;
        float halfHeight = m_Bounds.height * 0.5f;
        float xMid = m_Bounds.xMin + halfWidth;
        float yMid = m_Bounds.yMin + halfHeight;

        var leftBottomRect = new Rect(m_Bounds.xMin, m_Bounds.yMin, halfWidth, halfHeight);
        m_Children[0] = new MyQuadTree(leftBottomRect, m_SplitThreshold, m_MaxDepth);
        m_Children[0].m_Depth = nextDepth;

        var leftTopRect = new Rect(m_Bounds.xMin, yMid, halfWidth, halfHeight);
        m_Children[1] = new MyQuadTree(leftTopRect, m_SplitThreshold, m_MaxDepth);
        m_Children[1].m_Depth = nextDepth;

        var rightTopRect = new Rect(xMid, yMid, halfWidth, halfHeight);
        m_Children[2] = new MyQuadTree(rightTopRect, m_SplitThreshold, m_MaxDepth);
        m_Children[2].m_Depth = nextDepth;

        var rightBottomRect = new Rect(xMid, m_Bounds.yMin, halfWidth, halfHeight);
        m_Children[3] = new MyQuadTree(rightBottomRect, m_SplitThreshold, m_MaxDepth);
        m_Children[3].m_Depth = nextDepth;
    }

    private int[] m_TempIndex = new int[4];
    //获取包围盒所属的节点索引
    private int[] GetAABBBelongIndexes(Rect aabb)
    {
        int index = 0;
        if (aabb.Overlaps(m_Bounds))
        {
            float halfWidth = m_Bounds.width * 0.5f;
            float halfHeight = m_Bounds.height * 0.5f;
            float xMid = m_Bounds.xMin + halfWidth;
            float yMid = m_Bounds.yMin + halfHeight;
            var left = aabb.xMin < xMid;
            var right = aabb.xMax > xMid;
            var bottom = aabb.yMin < yMid;
            var top = aabb.yMax > yMid;

            if (left)
            {
                if (bottom)
                    m_TempIndex[index++] = 0;
                if (top)
                    m_TempIndex[index++] = 1;
            }
            if (right)
            {
                if (top)
                    m_TempIndex[index++] = 2;
                if (bottom)
                    m_TempIndex[index++] = 3;
            }
        }
        for (int i = index; i < 4; ++i)
            m_TempIndex[i] = -1;

        return m_TempIndex;
    }

    //添加到所属的节点, 可以属于多个节点
    private void AddShapeToBelong(IShape shape)
    {
        var shapeBounds = shape.GetBounds();
        var allIndexes = GetAABBBelongIndexes(shapeBounds);
        for (int i = 0; i < allIndexes.Length; ++i)
        {
            int index = allIndexes[i];
            if (-1 == index) break;
            m_Children[index].AddShape(shape);
        }
    }

    private HashSet<IShape> m_TempSet = new HashSet<IShape>();
    //获取与该包围盒发生碰撞的所有Shape
    public List<IShape> Query(Rect bounds)
    {
        m_TempSet.Clear();
        Query(bounds, m_TempSet);
        return new List<IShape>(m_TempSet);
    }

    private void Query(Rect bounds, HashSet<IShape> result)
    {
        //一个Shape可能会添加到多个节点, 用set达到去重效果
        for (int i = 0; i < m_ShapeList.Count; ++i)
            result.Add(m_ShapeList[i]);

        var allIndexes = GetAABBBelongIndexes(bounds);
        if (null != m_Children)
        {
            for (int i = 0; i < allIndexes.Length; ++i)
            {
                int index = allIndexes[i];
                if (-1 == index) break;
                m_Children[index].Query(bounds, result);
            }
        }
    }

    public void Clear()
    {
        m_ShapeList.Clear();

        if (null != m_Children)
        {
            for (int i = 0; i < m_Children.Length; ++i)
                m_Children[i].Clear();
            m_Children = null;
        }
    }

}

 

测试代码

using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class MyQuadTreeTest : MonoBehaviour
{
    public Transform m_ShapesRoot;
    public GameObject m_RectTemplate;

    [Range(0, 9)]
    public int m_ApiType = 1;

    public bool m_Benchmark = false; //测试调用多少次时, 执行耗时超过800ms
    [Range(0, 999)]
    public int m_TestShapeCount = 10;

    public int m_CostTime = 0;
    public int m_CheckCount = 0;

    private Vector3 m_PointCubeSize = new Vector3(0.1f, 0.1f, 0.01f);

    private List<OBBRect> m_RectList = new List<OBBRect>();
    private HashSet<OBBRect> m_MouseIntersectRect = new HashSet<OBBRect>(); //SceneView下鼠标下方要标红的矩形
    private MyQuadTree m_Tree;

    void Start()
    {
        m_Tree = new MyQuadTree(new Rect(-10, -10, 20, 20), 4, 4);
        for (int i = 0; i < m_ShapesRoot.childCount; ++i)
        {
            var shape = m_ShapesRoot.GetChild(i).GetComponent<OBBRect>();
            if (null == shape || !shape.isActiveAndEnabled) continue;
            m_RectList.Add(shape);
            m_Tree.AddShape(shape);
        }
    }

    void Update()
    {
        for (int i = 0; i < 10; ++i)
        {
            if (m_RectList.Count >= m_TestShapeCount) break;
            CreateRect();
        }

        switch (m_ApiType)
        {
        case 1:
            TreeCheckIntersect();
            break;

        }

        CheckTimeCost();
    }

    void TreeCheckIntersect()
    {
        m_Tree.Clear();
        for (int i = 0; i < m_RectList.Count; ++i)
        {
            var shape = m_RectList[i];
            if (m_MouseIntersectRect.Contains(shape))
                shape.m_GizmosColor = Color.red;
            else
                shape.m_GizmosColor = Color.white;
            m_Tree.AddShape(shape);
        }

        var t = DateTime.Now;
        m_CheckCount = 0;
        for (int i = 0; i < m_RectList.Count; ++i)
        {
            var shape = m_RectList[i];

            var intersectList = m_Tree.Query(shape.GetBounds());
            if (null != intersectList)
            {
                for (int j = 0; j < intersectList.Count; ++j)
                {
                    var shape_2 = intersectList[j] as OBBRect;
                    if (null == shape_2 || shape == shape_2) continue;
                    m_CheckCount++;

                    if (Shape2DHelper.IsPolygonIntersect(shape.GetVerts(), shape_2.GetVerts()))
                    {
                        shape.m_GizmosColor = Color.red;
                        shape_2.m_GizmosColor = Color.red;
                    }
                }
            }
        }

        m_CostTime = (int)(DateTime.Now - t).TotalMilliseconds;
    }

    protected void CheckTimeCost()
    {
        if (m_CostTime >= 200) //防止卡住
        {
            for (int i = 1; i <= 10; ++i)
            {
                var childIndex = m_ShapesRoot.childCount - i;
                if (childIndex < 0) break;
                m_ShapesRoot.GetChild(childIndex).gameObject.SetActive(false);
            }
        }

        if (m_Benchmark)
        {
            if (m_CostTime >= 100)
            {
                if (m_ApiType >= 2)
                {
                    m_ApiType = 1;
                    m_Benchmark = false;
                }
                else
                    m_ApiType++;
            }
            else
            {
                if (m_RectList.Count >= m_TestShapeCount)
                {
                    m_TestShapeCount++;
                }
            }
        }
    }

    private void CreateRect()
    {
        var go = GameObject.Instantiate(m_RectTemplate, m_ShapesRoot, false);
        go.SetActive(true);
        go.name = $"Rect ({m_RectList.Count})";
        var trans = go.transform;
        trans.position = new Vector2(UnityEngine.Random.Range(-10, 10), UnityEngine.Random.Range(-10, 10));
        trans.localEulerAngles = new Vector3(0, 0, UnityEngine.Random.Range(-30, 30));

        var shape = go.GetComponent<OBBRect>();
        shape.m_Width = UnityEngine.Random.Range(0.1f, 3f);
        shape.m_Height = shape.m_Width * UnityEngine.Random.Range(0.6f, 0.8f);
        m_RectList.Add(shape);
        m_Tree.AddShape(shape);
    }

    private void CreateRect2(Vector3 pos)
    {
        var go = GameObject.Instantiate(m_RectTemplate, m_ShapesRoot, false);
        go.SetActive(true);
        go.name = $"Rect ({m_RectList.Count})";
        var trans = go.transform;
        trans.position = pos;
        trans.localEulerAngles = new Vector3(0, 0, UnityEngine.Random.Range(-30, 30));

        var shape = go.GetComponent<OBBRect>();
        shape.m_Width = 2;
        shape.m_Height = 1.5f;
        m_RectList.Add(shape);
        m_Tree.AddShape(shape);
    }

#if UNITY_EDITOR

    private Stack<MyQuadTree> m_DrawStack = new Stack<MyQuadTree>();
    private void OnDrawGizmos()
    {
        DrawGizmos_Tree();
    }

    private void DrawGizmos_Tree()
    {
        if (null != m_Tree)
        {
            var curEvent = Event.current;
            var cam = Camera.current;
            if (null != cam)
            {
                Vector3 screenPos = HandleUtility.GUIPointToScreenPixelCoordinate(curEvent.mousePosition); //转换成像素单位以及左下角(0, 0)
                screenPos.z = -cam.transform.position.z;
                var worldPos = cam.ScreenToWorldPoint(screenPos);
                //Debug.Log($"mousePos:{curEvent.mousePosition} -> screenPos:{screenPos}, worldPos:{worldPos}");

                float handleSizeFactor = HandleUtility.GetHandleSize(worldPos);
                var pointCubeSize = m_PointCubeSize * handleSizeFactor;
                Gizmos.DrawCube((Vector2)worldPos, pointCubeSize);

                var mousePosBounds = new Rect(worldPos.x - m_PointCubeSize.x * 0.5f, worldPos.y - m_PointCubeSize.y * 0.5f, m_PointCubeSize.x, m_PointCubeSize.y);
                var intersectList = m_Tree.Query(mousePosBounds);
                m_MouseIntersectRect.Clear();
                if (null != intersectList)
                {
                    for (int i = 0; i < intersectList.Count; ++i)
                    {
                        var shape = intersectList[i] as OBBRect;
                        m_MouseIntersectRect.Add(shape);
                    }
                }
                if (curEvent.type == EventType.MouseUp) //在鼠标位置添加一个矩形
                {
                    CreateRect2(worldPos);
                    curEvent.Use();
                }
            }

            var node = m_Tree;
            m_DrawStack.Push(node);
            do
            {
                node = m_DrawStack.Pop();
                var c = Color.HSVToRGB(node.Depth / (float)node.MaxDepth, 1, 1);
                //c.a = 0.5f;
                Gizmos.color = c;
                DrawNodeBounds(node);

                for (int i = 0; i < 4; ++i)
                {
                    var child = node.GetChild(i);
                    if (null != child)
                        m_DrawStack.Push(child);
                }
            } while (m_DrawStack.Count > 0);

            Gizmos.color = Color.white;
        }
    }

    private void DrawNodeBounds(MyQuadTree node)
    {
        var bounds = node.Bounds;
        //显示的时候, 左下角padding缩小一点, 不然线叠在一起看不清
        var rootBounds = m_Tree.Bounds;
        float x = node.Depth * rootBounds.width * 0.003f;
        float y = node.Depth * rootBounds.height * 0.003f;
        bounds.xMin += x;
        bounds.yMin += y;

        var min = bounds.min;
        var max = bounds.max;
        var leftTop = new Vector2(min.x, max.y);
        var rightBottom = new Vector2(max.x, min.y);
        Gizmos.DrawLine(min, leftTop);
        Gizmos.DrawLine(leftTop, max);
        Gizmos.DrawLine(max, rightBottom);
        Gizmos.DrawLine(rightBottom, min);
    }

#endif

}

 

参考

timohausmann/quadtree-js: A lightweight quadtree implementation for javascript (github.com)

quadtree-js Dynamic Demo (timohausmann.github.io)

快速检索碰撞图形:四叉树碰撞检测 - 知乎 (zhihu.com)

 

标签:int,private,public,碰撞检测,shape,var,new,四叉树,加速
From: https://www.cnblogs.com/sailJs/p/17865149.html

相关文章

  • 「智造」第10期:浅谈工业互联网加速企业服务化转型
    01价值引领在新型工业化和数智化技术发展趋势之下,柔性化生产、网络化协同、服务化延伸促使制造企业的生产方式发生转变,数据资产、平台技术、生态能力成为了重要的生产要素。目标都是要为企业实现价值提升、业务的增长和创新。无论是纵向的从采购、制造到营销的运营管理链路,还是横向......
  • 三大核心成长驱动力,C海光:国产CPU领军,加速突破高端市场
    1.3“数字经济”及“东数西算”推动核心行业市场服务器国产替代数字经济是近年来提出的全新的国家战略,随着各行业数字化转型进程的加快,所产生的数据呈现指数式爆发增长,算力已成为数字经济发展的核心因素,而数据的传输、存储与计算都与服务器息息相关。2022年1月,国务院发布的......
  • 上海数交所与合合信息发布产业数据行业创新中心,政产学研合力为“数据航母”加速
    上海数交所与合合信息发布产业数据行业创新中心,政产学研合力为“数据航母”加速大数据产业是数字经济创新发展、加速发展的重要方向。11月25日,2023全球数商大会在上海盛大开幕。大会以“数联全球、商通未来”为主题,聚焦数字经济时代下,数据要素推动实体经济发展的规划与成果,是数......
  • 5G+车联网加速融合,看华测导航车高精度模组如何抢占市场高地!
     近年来,智能汽车领域的关注度一直居高不下,打开微博,经常可以看到华为汽车、比亚迪等热门车型的讨论。事实上,随着新能源汽车的快速崛起,汽车行业实现了智能化转型。目前,我国作为全球最大的车辆及出行服务市场,未来在智能驾驶应用领域有着广阔的发展空间,L2+辅助驾驶已逐步成为我国发......
  • 三大基础方案和AI出海计划重磅发布!加速盘古大模型生态发展
    本文分享自华为云社区《三大基础方案和AI出海计划重磅发布!加速盘古大模型生态发展》,作者:华为云头条。近日,以“开放同飞,共赢行业AI新时代”为主题的华为云盘古大模型主题论坛·深圳站成功举办。华为云与多位不同行业的客户和伙伴围绕AI大模型、技术创新应用和产业发展新机遇等话......
  • 【教程】cpp转python Nanobind 实践 加速轻量版 pythonbind11
    主要是尝试一下把c++这边的函数封装打包给python用,选择nanobind的原因是:1.优化速度快,2.生成二进制包小,不过pythonbind11是更为广泛知道的,nanobind也是pythonbind11作者后续做的,可以查看作者写的whyanotherbindinglibaray?总结一下就是:nanobind同样是一个用于创建C++和P......
  • Ubuntu中使用apt-fast加速apt的执行速度
    安装/bin/bash-c"$(curl-sLhttps://gitee.com/nanakura/apt-fast-mirror/raw/main/install.sh)"使用sudoapt-fastinstallgitbuild-essentialgdb-multiarchqemu-system-miscgcc-riscv64-linux-gnubinutils-riscv64-linux-gnu......
  • docker 常用命令、安装、镜像加速配置
    docker笔记,请参考。常用命令官方学习网站,生涩。网上资料千奇百怪,建议到官网验证。可以用AI学习一点,但经常有错,像文心一言、通义千问。https://docs.docker.com/engine/reference/run/以ubantu为例,你可以在docker安装一个ubantu容器。你首先是有要有一个镜像,可以在hub.do......
  • Windows rustup update 速度慢,使用字节跳动Rust镜像加速
    不设置镜像加速rustup更新升级会非常慢RsProxy字节跳动的Rust镜像 Windows想要使用这个镜像需要按照官方提示去设置两个系统变量分别为 RUSTUP_DIST_SERVER RUSTUP_UPDATE_ROOT 之后来到当前用户文件夹下修改cargo的配置文件(没有就创建一个)C:\Users\你PC名\.c......
  • 使用FP8加速PyTorch训练
    现代的人工智能硬件架构(例如,NvidiaHopper,NvidiaAdaLovelace和HabanaGaudi2)中,FP8张量内核能够显著提高每秒浮点运算(FLOPS),以及为人工智能训练和推理工作负载提供内存优化和节能的机会。在这篇文章中,我们将介绍如何修改PyTorch训练脚本,利用NvidiaH100GPU的FP8数据类型的......