Более производительное дерево квадрантов для движущихся и сталкивающихся объектов

В общем, я хочу создать сцену, в которой около 50 000 астероидов порождаются с позицией и AABB (граничной рамкой, выровненной по осям), и перемещать каждый из них в случайном направлении, которое генерируется в начале. Переместив их, я должен проверить, не сталкиваются ли какие-либо астероиды.

Я использую структуру данных Quad-Tree для вставки и проверки на столкновение. Я сохраняю массив из 50 000 точек, повторяю и обновляю их, затем вставляю в Quad-Tree и снова повторяю 50 000 точек и запрашиваю через 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
}



person Orif    schedule 15.06.2019    source источник
comment
Не могли бы вы предоставить некоторые данные о том, насколько толста ваша реализация и насколько быстрой она должна быть?   -  person TilmannZ    schedule 15.06.2019


Ответы (2)


О вашей реализации:

Кажется, что вы перестраиваете все дерево для каждого шага. Это необходимо? Если вы переместите точку, они часто останутся в одном и том же узле, поэтому вы можете избежать clearNodes() и последующей вставки в один и тот же узел.

Другие реализации:

Я реализовал некоторые пространственные индексы на Java со скоростью вставки/обновления около 1 млн точек в секунду и частотой запросов (проверка коллизий) 100 000 в секунду (при условии, что обычно происходит около 0 или 1 коллизия на точку. См. некоторые измерения производительности здесь (рис. 16b для 3D-запросов и Рисунок 40b для обновлений).Самые быстрые — это деревья квадрантов (см. qthypercube и qthypercube2) и PH-Tree. Все они используют навигация по z-порядку, как описано здесь (самореклама). Одна часть этого заключается в том, что он вычисляет правильные подузлы во время навигации. на/вставка/обновление/удаление. Например, при вызове вставки (элемента) на узле он быстро не проверяет все подузлы, а «вычисляет», какой подузел правильный, и напрямую вызывает вставку () на этом подузле.

person TilmannZ    schedule 15.06.2019

Новый ответ с дополнительными требованиями:

Итак, с 50 000 точек и 120 Гц вам нужно выполнять 50 000*120=6 000 000 проверок коллизий в секунду. Учитывая, что ЦП с тактовой частотой 4 ГГц, это означает, что у вас есть около 650 циклов ЦП на проверку коллизий. Я не думаю, что вы можете сделать это с quadtrees или чем-то подобным, даже с самым эффективным языком программирования.

Я вижу только один вариант: Поскольку вы используете 2D, попробуйте следующее: Отсортируйте все точки по их координате X. Затем пройдитесь по всем точкам и проверьте наличие столкновений со всеми точками, которые находятся достаточно близко по координате X, чтобы они могли вызвать столкновение. Такой алгоритм имеет ряд преимуществ:

  • Он гораздо более удобен для кэширования, чем пространственные индексы, и промахи кэша (доступ к памяти), скорее всего, являются узким местом.
  • Он легко распараллеливается (сортировка может быть в основном распараллелена, а поиск — в основном распараллелен).
  • Это достаточно просто, чтобы его можно было выполнить на графическом процессоре.

Одно ядро ​​​​ЦП, это все еще, вероятно, будет слишком медленным. Но используя 4-ядерную машину, вы можете получить желаемую частоту кадров. Используя графический процессор, можно получить даже гораздо больше, чем вам нужно. Однако у меня нет опыта использования графических процессоров, поэтому я не знаю, как (просто) это можно сделать.

person TilmannZ    schedule 16.06.2019