Лучшее производительное квад-дерево для движущихся и сталкивающихся объектов - PullRequest
0 голосов
/ 15 июня 2019

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

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

1 голос
/ 15 июня 2019

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

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

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

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

0 голосов
/ 16 июня 2019

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

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

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

  • Он гораздо более дружественен к кешу, чем пространственные индексы, и пропуски кеша (доступ к памяти), скорее всего, являются узким местом.
  • Он легко распараллеливается(сортировка может быть в основном распараллелена, а поиск - в основном распараллелен).
  • Достаточно просто, что она может быть выполнена на GPU.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...