• 八叉树分割3d 空间


    【八叉树:分割3D空间】

    <1>目标:使用八叉树分割3D空间,实现创建函数和插入函数

    <2>思路:

    <3>代码:

    以下树实现相关代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //封装插入的obj结构(根据项目自己定义)    public class OCObj
    {        //Unity Obj引用
        public GameObject gameObj;//        //物体半径(宽度)
        public float halfWidth;
        //center在obj身上取
        //public Vector3 center = Vector3.zero;
        //引用到的树节点lst 用于移除
        public Node linkNode;        //位置
        public Vector3 center
        {
            get
            {
                return gameObj.transform.position;
            }
        }
    }

      

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //节点定义(根据需求定义)
    public class Node
    {
        //节点中心
        public Vector3 center = Vector3.zero;
        //节点宽度
        public float halfWidth = 0;
        //缓存插入的Obj
        public List<OCObj> objLst = null;//需要再new
        //子节点 需要再new
        public List<Node> childs = new List<Node>();
    }

      

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    //创建树函数 中心 半宽 深度        public static Node BuildOCTree(Vector3 center, float halfWidth, int stopDepth)
    {
        if (stopDepth < 0) return null;
        else
        {
            Node node = new Node();
            node.center = center;
            node.halfWidth = halfWidth;
            //创建子节点  每个节点的3个轴偏移 即为+- halfWidth*0.5f
            Vector3 offset = Vector3.zero;
            float step = halfWidth * 0.5f;
            for (int i = 0; i < 8; i++)
            {
                //8个点
                //上下排序
                offset.y = i % 2 == 0 ? step : -step;
                offset.z = i <= 3 ? step : -step;
                offset.x = i <= 1 || i >= 6 ? step : -step;
                Node child = BuildOCTree(center + offset, step, stopDepth - 1);
                if (child != null)
                {
                    node.childs.Add(child);
                }
            }
            return node;
        }
    }

      

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    //插入函数 树根节点 Obj        public static void Insert(Node root, OCObj obj)
    {
        int index = 0;
        int x = 0;
        int y = 0;
        int z = 0;
        bool isPress = false;//是否占用了节点的2个格子
        Dictionary<intint> map = new Dictionary<intint>();
        for (int i = 0; i < 3; i++)
        {
            float delta = obj.center[i] - root.center[i];
            if (Mathf.Abs(delta) < obj.halfWidth)
            {
                isPress = true;
            }
            if (i == 0)
            {
                x = delta > 0 ? 1 : -1;
            }
            else if (i == 1)
            {
                y = delta > 0 ? 1 : -1;
            }
            else
            {
                z = delta > 0 ? 1 : -1;
            }
        }
        index = indexMap[z * 100 + y * 10 + x];
        //压线了 或者 没有再深的层次了 则加入到当前节点
        if (isPress || root.childs.Count <= 0)
        {
            if (root.objLst == null)
            {
                root.objLst = new List<OCObj>();
            }
            root.objLst.Add(obj);
            obj.linkNode = root;
        }
        else
        {
            Insert(root.childs[index], obj);
        }
    }
     
    static Dictionary<intint> indexMap = new Dictionary<intint>() {
        { 100+10+1,0 },//1  1  1 = 0
        { 100-10+1,1 },//1  0   1 = 1
        { 100+10-1,2 },//1   1  0  = 2
        { 100-10-1, 3 },//1   0  0 = 3
        { -100+10-1, 4 },//0  1  0 = 4   101 = 4
        { -100-10 -1, 5 },
        { -100+10 +1, 6 },
        { -100-10 +1, 7 },
    };

      

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //打印周围obj.name
    public static void PrintCloserObjs(Node node) {
        if (node.objLst != null && node.objLst.Count > 0) {
            for (int i = 0; i < node.objLst.Count; i++)
            {
                Debug.Log("name:  " + node.objLst[i].gameObj.name);
            }
        }
        if (node.childs != null && node.childs.Count > 0) {
            for (int i = 0; i < node.childs.Count; i++)
            {
                PrintCloserObjs(node.childs[i]);
            }
        }
    }

      以下树创建测试代码

      

    <4>核心

    创建函数中,2种创建方式,最终我们选取第二种方式创建子节点,因为我们需要更高效的插入

    1.红色代码计算8个节点中心 容易理解:8个节点在3D空间中分为2层,从上层第一象限Index=0到第二层第一象限Index=1到第一层第二象限到第二层第二象限到...

    2.蓝色代码用一种比较特殊的方式计算出8个节点中心

       8个节点的Index区间[0-7],而0-7可以用二进制表示为8个象限轴方向

       1,2,4的二进制可以表示三条坐标轴

       在遍历0-7的时候,每个数字的二进制与1,2,4求 & 运算

       可以求出每个节点的轴方向:我们下面以1计算X轴 2计算Y轴 4计算Z轴  1=0001  2=0010  4=0100

       例如节点index=0与 1,2,4 做&运算 0=0000   求出的三种&运算都为0000 所以index=0的节点坐标=父节点坐标 + Vector3(-step,-step,-step) 即位于第二层的第三象限

       例如节点index=1与 1,2,4 做&运算 0=0001   X轴为正 所以index=1的节点坐标=父节点坐标 + Vector3(step,-step,-step) 即位于第二层的第四象限

       例如节点index=2与 1,2,4 做&运算 0=0010   Y轴为正 所以index=1的节点坐标=父节点坐标 + Vector3(-step,step,-step) 即位于第一层的第三象限

       例如节点index=3与 1,2,4 做&运算 0=0011   XY轴为正 所以index=1的节点坐标=父节点坐标 + Vector3(step,step,-step) 即位于第一层的第四象限

       例如节点index=4与 1,2,4 做&运算 0=0100   Z轴为正 所以index=1的节点坐标=父节点坐标 + Vector3(-step,-step,step) 即位于第二层的第二象限

       例如节点index=5与 1,2,4 做&运算 0=0101   XZ轴为正 所以index=1的节点坐标=父节点坐标 + Vector3(step,-step,step) 即位于第二层的第一象限

       例如节点index=6与 1,2,4 做&运算 0=0110   YZ轴为正 所以index=1的节点坐标=父节点坐标 + Vector3(-step,step,step) 即位于第一层的第二象限

       例如节点index=7与 1,2,4 做&运算 0=0111   XYZ轴为正 所以index=1的节点坐标=父节点坐标 + Vector3(step,step,step) 即位于第一层的第一象限

      

    1
    2
    3
    4
    5
    6
    7
    8
    //0  0/2 = 00            0000 //XYZ轴为负
    //1  1/2 = 01            0001 //Z轴为正 也可以当做X轴
    //2  10   01             0010 //Y轴为正
    //3   11   01            0011 //YZ为正
    //4   20   10  01        0100 //X轴为正 也可以当做Z轴
    //5   21   10  01        0101 //XZ为正
    //6   30   11  01        0110 //XY为正
    //7   31   11  01        0111 //XYZ为正

      

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //创建子节点  每个节点的3个轴偏移 即为+- halfWidth*0.5f
    Vector3 offset = Vector3.zero;
    float step = halfWidth * 0.5f;
    for (int i = 0; i < 8; i++)
    {
        //8个点
        //上下排序
        offset.y = i % 2 == 0 ? step : -step;
        offset.z = i <= 3 ? step : -step;
        offset.x = i <= 1 || i >= 6 ? step : -step;
        //offset.x = (i & 1) > 0 ? step : -step;
        //offset.y = (i & 2) > 0 ? step : -step;
        //offset.z = (i & 4) > 0 ? step : -step;
        Node child = BuildOCTree(center + offset, step, stopDepth - 1);
        if (child != null)
        {
            node.childs.Add(child);
        }
    }

      

    插入函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    public static void Insert(Node root, OCObj obj)
    {
        int index = 0;
        int x = 0;
        int y = 0;
        int z = 0;
        bool isPress = false;//是否占用了节点的2个格子
        Dictionary<intint> map = new Dictionary<intint>();
        for (int i = 0; i < 3; i++)
        {
            float delta = obj.center[i] - root.center[i];
            if (Mathf.Abs(delta) < obj.halfWidth)
            {
                isPress = true;
            }
            if (i == 0)
            {
                x = delta > 0 ? 1 : -1;
            }
            else if (i == 1)
            {
                y = delta > 0 ? 1 : -1;
            }
            else
            {
                z = delta > 0 ? 1 : -1;
            }
        }
        index = indexMap[z * 100 + y * 10 + x];
        //压线了 或者 没有再深的层次了 则加入到当前节点
        if (isPress || root.childs.Count <= 0)
        {
            if (root.objLst == null)
            {
                root.objLst = new List<OCObj>();
            }
            root.objLst.Add(obj);
            obj.linkNode = root;
        }
        else
        {
            Insert(root.childs[index], obj);
        }
    }
     
    static Dictionary<intint> indexMap = new Dictionary<intint>() {
        { 100+10+1,0 },//1  1  1 = 0
        { 100-10+1,1 },//1  0   1 = 1
        { 100+10-1,2 },//1   1  0  = 2
        { 100-10-1, 3 },//1   0  0 = 3
        { -100+10-1, 4 },//0  1  0 = 4   101 = 4
        { -100-10 -1, 5 },
        { -100+10 +1, 6 },
        { -100-10 +1, 7 },
    };

      

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public static void Insert(Node root, OCObj obj)
    {
        int index = 0;
        bool isPress = false;//是否占用了节点的2个格子
        for (int i = 0; i < 3; i++)
        {
            float delta = obj.center[i] - root.center[i];
            if (Mathf.Abs(delta) < obj.halfWidth)
            {
                isPress = true;
                break;
            }
            if (delta > 0.0f) index |= (1 << i);
        }
        //压线了 或者 没有再深的层次了 则加入到当前节点
        if (isPress || root.childs.Count <= 0)
        {
            if (root.objLst == null)
            {
                root.objLst = new List<OCObj>();
            }
            root.objLst.Add(obj);
            obj.linkNode = root;
        }
        else
        {
            Insert(root.childs[index], obj);
        }
    }

      

    上面的2种插入函数对应了2种创建函数

    第一种插入函数比较容易理解:根据插入obj的坐标与节点坐标进行对比,确定XYZ的正负,组合成key = Z*100+Y*10+X,然后再字典中取对应的index

    第一种10000次测试,深度为4,平均开销为25ms(new字典 3次遍历 if else3次判断)

    第二种10000次测试,深度为4,平均开销为5ms(至多3次遍历,如果压线就break插入当前节点,两种方式中读取了gameObject的transform这个可以统一优化todo)

    第二种函数函数最难理解的是确定obj的index

    思路:

    遍历3次 i区间[0-2] 

    默认index=0 使用二进制表示为 0000三个轴都为负

    每次都进行了 1<<i 运算 可以发现三次运算结果是 1<<0 = 0001  1<<1 = 1*2^1 = 2 = 0010 1<<2 =  1*2^2 = 4 = 0100 即为XYZ三个轴 

    当i=0切obj.x-node.x > 0,index = 0000 | (1<<0) = 0000 | (1*2^0) = 0000 | 1 = 0000 | 0001 = 0001 =  1 

    第一次可以确定X 第二次可以确定Y 第三次可以确定Z 三次的index都是在与之前的index做 | 运算,可以求出并集,最终确定index

    ** 整体解读:

      1;创建八叉树网格思路 -- 分为八个方向 三维坐标系 xyz 的正负来区分  每个childs下面包含八个childs 直到最后一个 没有childs停止

      2;八叉树中添加物品 -- 每个区域有个objList  objList中保存本区域所有的物品 并且给每个obj 指定linkNode

    原作者 :https://www.cnblogs.com/cocotang/p/10824958.html

  • 相关阅读:
    Mapreduce学习(一)
    Codeforces Global Round 10题解(A-D)
    八月第二周总结
    hdfs学习(三)
    Educational Codeforces Round 93 (Rated for Div. 2)题解
    hdfs学习(二)
    牛客团队赛50&CF#664(Div2)
    hdfs学习(一)
    蓝桥杯刷题(二)
    Kubernetes K8S之Pod 生命周期与postStart、preStop事件
  • 原文地址:https://www.cnblogs.com/gwen-/p/13986761.html
Copyright © 2020-2023  润新知