四叉树分类

四叉树是一种空间划分树,每个节点最多有四个子树的数据结构。
主要应用于:

  • 场景管理:
    特别适合大规模的广阔室外场景管理。
    一般来说如果游戏场景是基于地形的(甚至没有高度)(如城市、平原、2D场景),那么适合用四叉树来管理。
    而如果游戏场景在高度轴上也有大量物体需要管理(如太空、高山),那么适合用八叉树来管理。

  • 感知检测
    假如保证一个智能体最远不会感知到所在区域外的地方。
    那么通过四叉树,可以快速过滤掉K区域外的目标,只需考虑K区域内的目标。

  • 碰撞检测
    类似上面感知检测。不同划分区域保证不会碰撞的情况下,就能快速过滤与本物体不同区域的其他潜在物体碰撞。

  • 光线追踪(Ray Tracing)过滤
    光线追踪渲染,可使用八叉树来划分3D空间区域,从而过滤掉大量不必要的区域。
    四叉树搜索和插入的效率为O(nlogn)

// 四叉树根节点,用于管理四叉树
public class Tree {
    public INode root;
    public List<INode> nodes;

    public int maxDepth;
    public int scale; // 树的规模

    /// <summary>
    /// 默认为四叉树构造方式, 可以修改Constructor方法实现不同的Tree类型
    /// </summary>
    public virtual void Constructor(Bounds boundary, int maxDepth=5, int scale=4) {
        this.maxDepth = maxDepth;

        int nodesCount = 1;
        for (int i = 1; i < maxDepth; ++i)
            nodesCount |= 1 << (i<<1);

        // 如果需要扩展Tree的功能, 可以修改这两个地方Node的实例化调用
        nodes = new List<INode>(new INode[nodesCount]);
        root = new Node(0, 1, boundary.center, boundary.size, scale);

        AppendNode(0, root);
    }

    public void Insert(NodeInfo nodeInfo) {
        root.Insert(this, nodeInfo, maxDepth);
    }

    public virtual void AppendNode(int nodeIndex, INode nodeInfo) {
        nodes[nodeIndex] = nodeInfo;
    }

    public void MoveNext(Camera camera) {
        root.MoveNext(camera);
    }

    public void DrawGizmos() {
        root.DrawGizmos();
    }
}
// 实现Node算法接口
public class Node : INode {
    public int id;
    public int index; // 节点的索引
    public int depth; // 节点的深度
    public int scale; // 节点规模
    public Bounds boundary;
    public List<NodeInfo> gameObjects;

    public int isRendering = 2;

    public bool divided; // 节点是否已经分裂
    public Node[] children; // 四叉树节点的子节点

    public Vector4[] corners; // 四叉树节点的边界点

    public Vector3 FLB { get => corners[0]; } // 前左下
    public Vector3 FLT { get => corners[1]; } // 前左上
    public Vector3 FRT { get => corners[2]; } // 前右上
    public Vector3 FRB { get => corners[3]; } // 前右下
    public Vector3 BRT { get => corners[4]; } // 后右上
    public Vector3 BRB { get => corners[5]; } // 后右下
    public Vector3 BLB { get => corners[6]; } // 后左下
    public Vector3 BLT { get => corners[7]; } // 后左上

    public Node(int index, int depth, Vector3 center, Vector3 size, int scale=4) {
        Constructor(index, depth, center, size, scale);
    }

    public void CalculateCorners() {
        corners[0].w = 1;
        corners[1].w = 1;
        corners[2].w = 1;
        corners[3].w = 1;
        corners[4].w = 1;
        corners[5].w = 1;
        corners[6].w = 1;
        corners[7].w = 1;

        corners[0].x = boundary.center.x - boundary.extents.x; // 前左下
        corners[0].y = boundary.center.y - boundary.extents.y; // 前左下
        corners[0].z = boundary.center.z - boundary.extents.z; // 前左下

        corners[1].x = boundary.center.x - boundary.extents.x; // 前左上
        corners[1].y = boundary.center.y + boundary.extents.y; // 前左上
        corners[1].z = boundary.center.z - boundary.extents.z; // 前左上

        corners[2].x = boundary.center.x - boundary.extents.x; // 前右上
        corners[2].y = boundary.center.y + boundary.extents.y; // 前右上
        corners[2].z = boundary.center.z + boundary.extents.z; // 前右上

        corners[3].x = boundary.center.x - boundary.extents.x; // 前右下
        corners[3].y = boundary.center.y - boundary.extents.y; // 前右下
        corners[3].z = boundary.center.z + boundary.extents.z; // 前右下

        corners[4].x = boundary.center.x + boundary.extents.x; // 后右上
        corners[4].y = boundary.center.y + boundary.extents.y; // 后右上
        corners[4].z = boundary.center.z + boundary.extents.z; // 后右上

        corners[5].x = boundary.center.x + boundary.extents.x; // 后右下
        corners[5].y = boundary.center.y - boundary.extents.y; // 后右下
        corners[5].z = boundary.center.z + boundary.extents.z; // 后右下

        corners[6].x = boundary.center.x + boundary.extents.x; // 后左下
        corners[6].y = boundary.center.y - boundary.extents.y; // 后左下
        corners[6].z = boundary.center.z - boundary.extents.z; // 后左下

        corners[7].x = boundary.center.x + boundary.extents.x; // 后左上
        corners[7].y = boundary.center.y + boundary.extents.y; // 后左上
        corners[7].z = boundary.center.z - boundary.extents.z; // 后左上
    }

    public void Constructor(int index, int depth, Vector3 center, Vector3 size, int scale=4) {
        this.index = index;
        this.depth = depth;
        this.scale = scale;

        boundary.center = center;
        boundary.size = size;

        gameObjects = new List<NodeInfo>(128);

        isRendering = 2;

        divided = false;
        corners = new Vector4[8];

        CalculateCorners();
    }

    public void Divide(Tree tree) {
        this.divided = true;
        children = new Node[scale];

        Vector3 center, size = boundary.size*0.5f;
        size.y = boundary.size.y;
        center.y = 0;

        center.x = boundary.center.x - boundary.extents.x*0.5f;
        center.z = boundary.center.z - boundary.extents.z*0.5f;
        children[0] = new Node((index<<2) + 1, depth+1, center, size);

        center.x = boundary.center.x + boundary.extents.x*0.5f;
        center.z = boundary.center.z - boundary.extents.z*0.5f;
        children[1] = new Node((index<<2) + 2, depth+1, center, size);

        center.x = boundary.center.x + boundary.extents.x*0.5f;
        center.z = boundary.center.z + boundary.extents.z*0.5f;
        children[2] = new Node((index<<2) + 3, depth+1, center, size);

        center.x = boundary.center.x - boundary.extents.x*0.5f;
        center.z = boundary.center.z + boundary.extents.z*0.5f;
        children[3] = new Node((index<<2) + 4, depth+1, center, size);

        tree.AppendNode(children[0].index, children[0]);
        tree.AppendNode(children[1].index, children[1]);
        tree.AppendNode(children[2].index, children[2]);
        tree.AppendNode(children[3].index, children[3]);
    }

    public void Insert(Tree tree, NodeInfo nodeInfo, int maxDepth) {
        if (!divided && depth < maxDepth)
            Divide(tree);
        
        int insertIdx = -1;
        if (divided) {
            for (int i = 0; i < scale; ++i) {
                if (children[i].boundary.Intersects(nodeInfo.boundary)) {
                    if (insertIdx != -1) {
                        insertIdx = -1;
                        break;
                    }
                    insertIdx = i;
                }
            }
        }

        if (insertIdx == -1) {
            gameObjects.Add(nodeInfo);
        } else {
            children[insertIdx].Insert(tree, nodeInfo, maxDepth);
        }
    }

    public void Destroy() {
        foreach (NodeInfo nodeInfo in gameObjects) {
            nodeInfo.gameObject.SetActive(true);
        }
        gameObjects.Clear();

        foreach (var child in children) {
            child.Destroy();
        }
        divided = false;
    }

    public void MoveNext(Camera camera) {
        if (CameraSight.Visible(corners, camera))
            Render(camera);
        else
            Culling(camera);
    }

    public void Render(Camera camera) {
        if (divided) {
            foreach (var child in children) {
                if (CameraSight.Visible(child.corners, camera)) {
                    child.Render(camera);
                } else {
                    child.Culling(camera);
                }
            }
        }

        if (isRendering == 0)
            return;
        isRendering = 0;
        for (int i = 0; i < gameObjects.Count; ++i) {
            if (gameObjects[i] != null && gameObjects[i].gameObject)
                gameObjects[i].gameObject.SetActive(true);
        }
    }

    public void Culling(Camera camera) {
        if (divided) {
            foreach (var child in children) {
                child.Culling(camera);
            }
        }

        if (isRendering == 1)
            return;
        isRendering = 1;
        for (int i = 0; i < gameObjects.Count; ++i) {
            if (gameObjects[i] != null && gameObjects[i].gameObject)
                gameObjects[i].gameObject.SetActive(false);
        }
    }

    public void DrawGizmos() {
        if (isRendering == 0) {
            Gizmos.color = Color.yellow;
        } else if (gameObjects.Count > 0) {
            Gizmos.color = Color.blue;
        } else {
            Gizmos.color = Color.green;
        }
        Gizmos.DrawWireCube(boundary.center, boundary.size);

        if (!divided || (isRendering & -isRendering) != 0)
            return;

        var to = boundary.center + Vector3.up*3;

        Gizmos.color = Color.red;
        foreach (var child in gameObjects) {
            if (child != null && child.gameObject)
                Gizmos.DrawLine(child.gameObject.transform.position, to);
        }

        foreach (var child in children) {
            child.DrawGizmos();
        }
    }
}
// 实现计算点在相机范围内的计算
public static class CameraSight {
    public static void IsInView(Vector4 position, ref int inViewport) {
        int visible = 0;
        if (position.x < -position.w) visible |= 1;
        if (position.x > position.w) visible |= 2;
        if (position.y < -position.w) visible |= 4;
        if (position.y > position.w) visible |= 8;
        if (position.z < -position.w) visible |= 16;
        if (position.z > position.w) visible |= 32;
        inViewport &= visible;
    }

    public static bool Visible(Vector4[] corners, Camera camera, float fov=1.0f) {
        int visible = 63;
        IsInView(camera.projectionMatrix * camera.worldToCameraMatrix * corners[0] * fov, ref visible);
        IsInView(camera.projectionMatrix * camera.worldToCameraMatrix * corners[1] * fov, ref visible);
        IsInView(camera.projectionMatrix * camera.worldToCameraMatrix * corners[2] * fov, ref visible);
        IsInView(camera.projectionMatrix * camera.worldToCameraMatrix * corners[3] * fov, ref visible);
        IsInView(camera.projectionMatrix * camera.worldToCameraMatrix * corners[4] * fov, ref visible);
        IsInView(camera.projectionMatrix * camera.worldToCameraMatrix * corners[5] * fov, ref visible);
        IsInView(camera.projectionMatrix * camera.worldToCameraMatrix * corners[6] * fov, ref visible);
        IsInView(camera.projectionMatrix * camera.worldToCameraMatrix * corners[7] * fov, ref visible);
        return visible == 0;
    }
}
// 生成测试Unity代码
public class Test : MonoBehaviour {
    public Bounds bounds;
    public int maxDepth;
    public int generateCount;

    public GameObject prefab;

    public QuadTree tree;


    // Start is called before the first frame update
    void Start() {
        tree = new QuadTree();
        tree.Constructor(bounds, maxDepth);
        RandomGenerator();
    }

    void RandomGenerator() {
        for (int i = 0; i < generateCount && Application.isPlaying; ++i) {
            GameObject go = Instantiate(prefab);
            go.name = $"Point {i}";
            go.transform.localScale = new Vector3(Random.Range(0.3f, 3), Random.Range(0.3f, 3), Random.Range(0.3f, 3));
            go.transform.position = new Vector3(UnityEngine.Random.Range(-bounds.size.x * 0.5f, bounds.size.x * 0.5f), go.transform.localScale.y*0.5f, UnityEngine.Random.Range(-bounds.size.z * 0.5f, bounds.size.z * 0.5f));
            tree.Insert(new NodeInfo(go));
        }
    }

    // Update is called once per frame
    void Update() {
        if (tree != null) {
            tree.MoveNext(Camera.main);
        }
    }

    void OnDrawGizmos() {
        if (tree != null) {
            tree.DrawGizmos();
        }
    }
}

四叉树区域管理

以上,只实现了四叉树的插入及区域控制代码,黄色区域为相机显示区域,蓝色区域为存在物体未显示区域,绿色为未显示区域。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值