c#四叉树效率

本文关键字:效率 四叉树 | 更新日期: 2023-09-27 18:13:33

我对unity比较陌生,所以我决定开始用c#编写一个小的平台物理引擎,其中主要的属性结构是四叉树。不幸的是,我的生成代码非常慢,花了4秒多的时间才为10K个对象(普通的c#对象,而不是GameObjects)构建一个四叉树。

代码与普通的四叉树略有不同;与将重叠区域的对象存储在根节点中不同,引用存储在包含该对象的每个子节点中。由于我所有的对象都是相对相同的大小,并且节点的最小大小是这个大小的两倍,因此以这种方式构建树可以将碰撞算法的效率从~O(N^(3/2))提高到~O(N)。

我的简单四叉树类如下所示:

using UnityEngine;
 using System.Collections;
 using System.Collections.Generic;
 using System;
 public class QuadTree {
     public QuadTree parent = null;
     public QuadTree[] childern = null;
     public List<PPObject> objectList = null;
     public BBox bbox = null;
     public bool leaf = true;
     public float minSize = 1.0f;
     public float maxObjs = 4;
     public float width;
     public float height;
     public Vector3 position;
     public int level;
     public QuadTree(QuadTree parent, BBox bbox, int level)
     {
         this.parent = parent;
         this.bbox = bbox;
         this.level = level;
         width = bbox.xmax - bbox.xmin;
         height = bbox.ymax - bbox.ymin;
         position = new Vector3(bbox.xmin + width * 0.5f, bbox.ymin + height * 0.5f, 0.0f);
         objectList = new List<PPObject>();
     }
     public void Subdivide()
     {
         Profiler.BeginSample("Subdivide");
         float x1 = bbox.xmin;
         float x2 = bbox.xmin + 0.5f * width;
         float x3 = bbox.xmax;
         float y1 = bbox.ymin;
         float y2 = bbox.ymin + height * 0.5f;
         float y3 = bbox.ymax;
         Profiler.BeginSample("allocate new quadtrees");
         childern = new QuadTree[4];
         QuadTree tl = new QuadTree(this, new BBox(x1, x2, y2, y3), level + 1);
         QuadTree tr = new QuadTree(this, new BBox(x2, x3, y2, y3), level + 1);
         QuadTree br = new QuadTree(this, new BBox(x2, x3, y1, y2), level + 1);
         QuadTree bl = new QuadTree(this, new BBox(x1, x2, y1, y2), level + 1);
         childern[0] = tl;
         childern[1] = tr;
         childern[2] = br;
         childern[3] = bl;
         Profiler.EndSample();
         PushToChildern();
         leaf = false;
         Profiler.EndSample();
     }
     public void PushToChildern()
     {
         Profiler.BeginSample("pushToChildern");
         foreach (QuadTree child in childern)
         {
             foreach (PPObject obj in objectList)
             {
                 child.AddObject(obj);
             }
         }
         objectList = null;
         Profiler.EndSample();
     }
     public void AddObject(PPObject obj)
     {
         Profiler.BeginSample("addObject");
         if (childern == null)
         {
             if (obj != null)
             {
                 float x1 = obj.position.x;
                 float w1 = obj.bbox.xmax - obj.bbox.xmin;
                 float y1 = obj.position.y;
                 float h1 = obj.bbox.ymax - obj.bbox.ymin;
                 float dx = Math.Abs(x1-position.x);
                 float dy = Math.Abs(y1-position.y);
                 if (dx < ((width+w1) * 0.5f) && dy < (height + h1) *0.5f )
                 {
                     objectList.Add(obj);
                 }
             }
             if (objectList.Count > maxObjs && width > minSize && height > minSize)
             {
                 Subdivide();
             }
         } else {
             foreach (QuadTree child in childern)
             {
                 child.AddObject(obj);
             }
         }
         Profiler.EndSample();
     }
 }

这里BBox和PPObject只是包含位置,边界,速度等的简单类。前面提到的4秒执行时间是在没有详细的对象稀疏性分析器的情况下执行的,对象稀疏性在0.99到0.4之间。如果有人能帮我理解为什么这么慢,那就太好了。大概有100K个函数调用,20K个实例化,总共3.4 Mb。也许我只是太习惯用Fortran编写了,但这对于在~10 GFlop的核心上运行似乎很荒谬。

谢谢!

c#四叉树效率

基本上,我首先实例化了树的深度,而不是宽度。这导致我制作了许多额外的(基本上是未起诉的)相关列表副本。下面的代码在我的机器上执行大约50ms,减少了100倍。经过一些调整,它应该适合在游戏引擎中使用。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
public class QuadTree
{
    public QuadTree parent = null;
    public QuadTree[] childern = null;
    public List<PPObject> objectList = null;
    public BBox bbox = null;
    public bool leaf = true;
    public float minSize = 1.0f;
    public float maxObjs = 4;
    public float width;
    public float height;
    public Vector3 position;
    public int level;
    public QuadTree(QuadTree parent, BBox bbox, List<PPObject> objectList, int level)
    {
        this.parent = parent;
        this.bbox = bbox;
        this.level = level;
        width = bbox.xmax - bbox.xmin;
        height = bbox.ymax - bbox.ymin;
        position = new Vector3(bbox.xmin + width * 0.5f, bbox.ymin + height * 0.5f, 0.0f);
        this.objectList = objectList;
        BuildTree();
    }
    public QuadTree(QuadTree parent, BBox bbox, int level)
    {
        this.parent = parent;
        this.bbox = bbox;
        this.level = level;
        width = bbox.xmax - bbox.xmin;
        height = bbox.ymax - bbox.ymin;
        position = new Vector3(bbox.xmin + width * 0.5f, bbox.ymin + height * 0.5f, 0.0f);
        objectList = new List<PPObject>();
    }
    public void Subdivide()
    {
        if (objectList.Count > maxObjs && childern == null && width > minSize && height > minSize)
        {
            float x1 = bbox.xmin;
            float x2 = bbox.xmin + 0.5f * width;
            float x3 = bbox.xmax;
            float y1 = bbox.ymin;
            float y2 = bbox.ymin + height * 0.5f;
            float y3 = bbox.ymax;
            childern = new QuadTree[4];
            BBox tlBBox = new BBox(x1, x2, y2, y3);
            BBox trBBox = new BBox(x2, x3, y2, y3);
            BBox brBBox = new BBox(x2, x3, y1, y2);
            BBox blBBox = new BBox(x1, x2, y1, y2);
            QuadTree tl = new QuadTree(this, tlBBox, level + 1);
            QuadTree tr = new QuadTree(this, trBBox, level + 1);
            QuadTree br = new QuadTree(this, brBBox, level + 1);
            QuadTree bl = new QuadTree(this, blBBox, level + 1);
            foreach (PPObject obj in objectList)
            {
                tl.AddObject(obj);
                tr.AddObject(obj);
                br.AddObject(obj);
                bl.AddObject(obj);
            }
            childern[0] = tl;
            childern[1] = tr;
            childern[2] = br;
            childern[3] = bl;
            objectList = new List<PPObject>();
            leaf = false;
        }
        else if (childern != null)
        {
            PushToChildern();
        }
    }
    public void PushToChildern()
    {
        foreach (QuadTree child in childern)
        {
            foreach (PPObject obj in objectList)
            {
                child.AddObject(obj);
            }
        }
        objectList = null;
    }
    public bool CheckBounds(PPObject obj, BBox region)
    {
        float x1 = obj.position.x;
        float w1 = obj.bbox.xmax - obj.bbox.xmin;
        float y1 = obj.position.y;
        float h1 = obj.bbox.ymax - obj.bbox.ymin;
        float dx = Math.Abs(x1 - position.x);
        float dy = Math.Abs(y1 - position.y);
        if (dx < ((width + w1) * 0.5f) && dy < (height + h1) * 0.5f)
        {
            return true;
        }
        return false;
    }
    /*
     * Make this faster by building the list to push here
     */
    public void AddObject(PPObject obj)
    {
        if (obj != null)
        {
            if (CheckBounds(obj, bbox))
            {
                objectList.Add(obj);
            }
        }
    }
    public void BuildTree()
    {
        Subdivide();
        if (childern != null)
        {
            foreach(QuadTree child in childern)
            {
                child.BuildTree();
            }
        }
    }
}