性能更好的四叉树,用于移动和碰撞对象

时间:2019-06-15 08:57:19

标签: c# performance unity3d data-structures quadtree

因此,基本上我想创建一个场景,在该场景中生成约50K个小行星,并带有一个位置和AABB(轴对齐边界框),并将它们中的每一个移动到开始时生成的随机方向。将它们移动后,我必须检查是否有小行星碰撞。

我正在使用四叉树数据结构进行插入和碰撞检查。 我保留了一个由5万个点组成的数组,并对其进行迭代和更新,然后将其插入四叉树中,然后再次对5万个点进行迭代,并通过QT进行查询以查看是否有任何点在碰撞。

我在这里和那里读了很多书,大约2周时间,并尝试了尽可能多的资料,但是我无法挤出最大的表现。大多数资源都使用c ++或其他性能更好的语言,但是我需要使用C#来实现。任何改善性能的建议都将不胜感激。

这是我的代码:

public struct Point
{
    public float x,y; 
    public int ID;

    public void MoveTowards(float posX, float posY)
    {
        position.x = position.x + posX;
        position.y = position.y + posY;
    }
}

public class Controller
{

    Point[] asteroids = new Point[50K];
    Point[] speed = new Point[50K];
    QuadTree qt = new QuadTree();

    //Runs every frame
    void Update() 
    {
        qt.ClearAllNodes();
        for loop asteroids(50K)
        {
            asteroids[i].MoveTowards(speed.x, speed.y);
            qt.Insert(astetoids[i]);
        }

        for loop asteroids(50K)
        {
            int collidingAsteroidID = qt.Query(astetoids[i]);
            if(collidingAsteroidID != -1) { 
                print(collidingAsteroidID + " is colliding with " + i); 
            }
        }
    }

}

class QuadTree 
{
    public Rectangle boundry;
    Point[] nodes;
    bool root = false;
    bool divided = false;
    int numberOfNodesInserted = 0;
    QuadTree northEast, northWest, southEast, southWest;

    public QuadTree(Rectangle boundry) 
    {
        nodes = new Point[4];
        this.boundry = boundry;
    }   

    #region Methods

    //Clear all the nodes in the Quad-Tree
    public void ClearAllNodes() 
    {
        if(numberOfNodesInserted == 0 && !root) return;
        numberOfNodesInserted = 0;
        root = false;

        if(divided) 
        {
            northEast.ClearAllNodes();
            northWest.ClearAllNodes();
            southEast.ClearAllNodes();
            southWest.ClearAllNodes();
        }
        divided = false;
    }

    public bool Insert(Point point) 
    {
        //Checking if the position is in the boundries.
        if(!boundry.Contains(point)) return false;
        if(numberOfNodesInserted < 4 && !root) 
        {
            nodes[numberOfNodesInserted] = point;
            numberOfNodesInserted++;
            return true;
        }
        else if(root)
        {
            if(northEast.Insert(point)) return true;            
            if(northWest.Insert(point)) return true;        
            if(southEast.Insert(point)) return true;
            if(southWest.Insert(point)) return true;    
        }
        else if(!root && numberOfNodesInserted == 4)
        {
            //Making this node a branch and moving all the to sub-leafs 
            root = true;
            numberOfNodesInserted = 0;

            if(!divided)
                SubDivide();

            for (int i = 0; i < 4; i++)
            {
                if(!northEast.Insert(nodes[i]))         
                if(!northWest.Insert(nodes[i]))     
                if(!southEast.Insert(nodes[i]))
                if(!southWest.Insert(nodes[i])) { }
            }

            if(!northEast.Insert(point))            
            if(!northWest.Insert(point))        
            if(!southEast.Insert(point))
            if(!southWest.Insert(point)) { }
            return true;
        }
        return false;
    }

    public int Query(Point point, float radius)
    {

        if(numberOfNodesInserted == 0 && !root) return -1;
        if(!boundry.Contains(point)) return -1;

        if(!root && numberOfNodesInserted != 0)
        {
            for (int i = 0; i < numberOfNodesInserted; i++)
            {
                if(DoOverlap(nodes[i], point, radius)) 
                    return nodes[i].ID; 
            }
        }
        else if(root && numberOfNodesInserted == 0)
        {
            int result;
            result = northEast.Query(point);
            if(result != -1)  return result;

            result = northWest.Query(point);
            if(result != -1)  return result;

            result = southEast.Query(point);
            if(result != -1)  return result;

            result = southWest.Query(point);
            if(result != -1)  return result;
        }
        return -1;
    }
    #endregion

    #region HelperMethods
    private void SubDivide() 
    {
        //Size of the sub boundries 
        if(northEast == null) 
        {   
            float x = boundry.x;
            float y = boundry.y;
            float r = boundry.radius / 2;

            northEast = new QuadTree(new Rectangle(x + r, y + r, r));
            northWest = new QuadTree(new Rectangle(x - r, y + r, r));
            southEast = new QuadTree(new Rectangle(x + r, y - r, r));
            southWest = new QuadTree(new Rectangle(x - r, y - r, r));
        } 
        divided = true; 
    }


    #endregion
}


2 个答案:

答案 0 :(得分:1)

关于您的实施:

似乎您正在为每一步重建整个树。这有必要吗?如果移动一个点,它们通常将留在同一节点中,因此可以避免clearNodes()以及随后插入同一节点的情况。

其他实现:

我已经用Java实现了一些空间索引,插入/更新速率约为1M点/秒,查询速率(冲突检查)为每秒100,000(假设每个点通常有0或1个碰撞)。性能测量here(图16b用于3D查询,图40b用于更新)。 最快的是四叉树(请参见qthypercube and qthypercube2)和PH-Tree。 它们都使用here(自广告)中所述的z顺序导航。其中的一部分是它在导航/插入/更新/删除期间计算正确的子节点。例如,在节点上调用insert(element)时,它不会快速尝试所有子节点,而是“计算”哪个子节点正确,然后直接在该子节点上调用insert()。

答案 1 :(得分:0)

有其他要求的新答案:

好的,所以对于50K点和120Hz,您需要每秒进行50,000 * 120 = 6,000,000个碰撞检查。考虑到具有4GHz的CPU,这意味着每次冲突检查大约需要650个CPU周期。我认为即使使用最高效的编程语言,您也不能使用四叉树或类似的东西来做到这一点。

我只看到一个选项: 由于您使用的是2D,请尝试以下操作:按其X坐标对所有点进行排序。然后遍历所有点,并检查与X坐标上足够接近的所有点是否可能发生碰撞的碰撞。这种算法有一些优点:

  • 与空间索引相比,它对缓存更友好,并且缓存未命中(内存访问)很可能是瓶颈。
  • 它很容易并行化(排序可以并行化,搜索可以并行化)。
  • 它非常简单,可以在GPU上执行。

一个CPU内核,这可能仍然太慢。但是使用4核计算机,您可能会获得所需的帧速率。使用GPU,可能会获得比您所需更多的东西。但是,我没有使用GPU的经验,所以我不知道如何(轻松)完成这项工作。