Как непрерывно находить все объекты в радиусе?

14

У меня очень большое количество сущностей (единиц). На каждом шаге каждый юнит должен знать позиции всех юнитов рядом с ним (расстояние меньше заданной константы R ). Все подразделения движутся непрерывно. Это в 3D.

В среднем, это будет 1% от общего числа единиц рядом с любой другой данной единицей с данными ограничениями.

Как я могу сделать это эффективно, без брутфорса?

OCyril
источник
7
Вам понадобится какая-то система пространственного разделения: en.wikipedia.org/wiki/Space_partitioning
Tetrad

Ответы:

15

Используйте один из распространенных алгоритмов разделения пространства, таких как Quadtree, Octree, BSP tree или даже простая Grid System. У каждого есть свои плюсы и минусы для каждого конкретного сценария. Вы можете прочитать больше о них в этих книгах .

Как правило (или, как я слышал, я не слишком знаком с причинами этого), Quadtree или Octree лучше подходят для наружной среды, в то время как дерево BSP лучше подходит для внутренних сцен. И выбор между использованием Quadtree или Octree зависит от того, насколько плоский ваш мир. Если есть небольшая разница в оси Y, использование Octree было бы расточительным. Octree - это в основном Quadtree с дополнительным измерением.

Наконец, не пренебрегайте простотой решения Grid. Многие люди игнорируют, что простой сетки иногда может быть достаточно (и даже более эффективным) для своих проблем, и вместо этого переходят прямо к более сложному решению.

Использование сетки состоит просто в разделении мира на равномерно распределенные регионы и хранении сущностей в соответствующем регионе мира. Затем, с учетом позиции, поиск соседних объектов будет происходить путем итерации по регионам, которые пересекают ваш радиус поиска.

Допустим, ваш мир варьировался от (-1000, -1000) до (1000, 1000) в плоскости XZ. Например, вы можете разделить его на сетку 10x10, например, так:

var grid = new List<Entity>[10, 10];

Затем вы поместите сущности в соответствующие ячейки сетки. Например, объект с XZ (-1000, -1000) попадет в ячейку (0,0), а объект с XZ (1000, 1000) попадет в ячейку (9, 9). Затем, учитывая положение и радиус в мире, вы можете определить, какие ячейки пересекаются этим «кругом», и выполнять итерацию только по ним с простым двойным значением для.

В любом случае, изучите все альтернативы и выберите ту, которая лучше всего подходит для вашей игры. Я признаю, что до сих пор не достаточно разбираюсь в предмете, чтобы решить, какой из алгоритмов будет для вас наилучшим.

Находите это на другом форуме, и это может помочь вам с решением:

Сетки работают лучше всего, когда подавляющее большинство объектов помещается в квадрат сетки, и распределение является довольно однородным. И наоборот, квадродерево работает, когда объекты имеют переменные размеры или сгруппированы в небольших областях.

Учитывая ваше расплывчатое описание проблемы, я склоняюсь и к сеточному решению (то есть если предположить, что единицы являются небольшими и довольно однородно распределены).

Дэвид Гувея
источник
Спасибо за подробный ответ. Да, кажется, что простое Grid-решение достаточно для меня.
OCyril
0

Я написал это некоторое время назад. Это теперь на коммерческом сайте, но вы можете получить источник для личного использования бесплатно. Это может быть излишним и написано на Java, но хорошо документировано, поэтому его не должно быть слишком сложно обрезать и переписать на другом языке. Он в основном использует Octree с настройками для обработки действительно больших объектов и многопоточности.

Я обнаружил, что Octree предлагает лучшее сочетание гибкости и эффективности. Я начал с сетки, но было невозможно правильно подобрать размеры квадратов, а большие участки пустых квадратов занимали пространство и вычислительные мощности даром. (И это было только в двух измерениях.) Мой код обрабатывает запросы из нескольких потоков, что значительно усложняет задачу, но документация должна помочь вам обойти это, если вам это не нужно.

RalphChapin
источник
0

Чтобы повысить свою эффективность, попробуйте тривиально отклонить 99% «юнитов», которые не находятся рядом с целевым юнитом, используя очень недорогой ограничивающий флажок. И я надеюсь, что вы могли бы сделать это без пространственной структуризации ваших данных. Поэтому, если все ваши юниты хранятся в единой структуре данных, вы можете попытаться обойти ее от начала до конца и, во-первых, проверить, находится ли текущая единица за пределами ограничивающей рамки интересующей единицы.

Определите ограничивающий прямоугольник для интересующей единицы, чтобы он мог безопасно отбрасывать предметы, которые не могут считаться «рядом» с ним. Проверка на исключение из ограничивающей рамки может быть дешевле, чем проверка радиуса. Однако в некоторых системах, где это было проверено, было установлено, что это не так. Два выступают почти одинаково. Это отредактировано после большого обсуждения ниже.

Первый: 2D ограничивающий прямоугольник.

// returns true if the circle supplied is completely OUTSIDE the bounding box, rectClip
bool canTrivialRejectCircle(Vertex2D& vCentre, WorldUnit radius, Rect& rectClip) {
  if (vCentre.x + radius < rectClip.l ||
    vCentre.x - radius > rectClip.r ||
    vCentre.y + radius < rectClip.b ||
    vCentre.y - radius > rectClip.t)
    return true;
  else
    return false;
}

По сравнению с чем-то вроде этого (в 3D):

BOOL bSphereTest(CObject3D* obj1, CObject3D* obj2 )
{
  D3DVECTOR relPos = obj1->prPosition - obj2->prPosition;
  float dist = relPos.x * relPos.x + relPos.y * relPos.y + relPos.z * relPos.z;
  float minDist = obj1->fRadius + obj2->fRadius;
  return dist <= minDist * minDist;
}.

Если объект не отклоняется тривиально, вы выполняете более дорогой и точный тест столкновения. Но вы просто ищете близость, поэтому сферный тест подходит для этого, но только для 1% объектов, которые переживают тривиальное отклонение.

Эта статья поддерживает поле для тривиального отклонения. http://www.h3xed.com/programming/bounding-box-vs-bounding-circle-collision-detection-performance-as3

Если этот линейный подход не дает нужной вам производительности, то может потребоваться иерархическая структура данных, о которой говорили другие авторы. R-деревья заслуживают рассмотрения. Они поддерживают динамические изменения. Они являются деревьями пространственного мира.

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

Помните, что сетка представляет собой одноуровневую структуру пространственных данных. Этот предел означает, что он не является действительно масштабируемым. По мере того, как мир увеличивается в размерах, увеличивается и количество клеток, которое необходимо покрыть. В конечном итоге само количество ячеек становится проблемой производительности. Однако для мира определенного размера это даст вам огромный прирост производительности без пространственного разделения.

Киран
источник
1
ОП конкретно сказал, что хочет избежать подхода грубой силы, который именно то, что вы описываете в своем первом абзаце. Кроме того, как вы считаете, что проверка ограничивающего прямоугольника дешевле проверки ограничивающей сферы ?! Это просто неправильно.
notlesh
Да, я знаю, что он хочет избежать грубой силы, которой можно было бы избежать, если попытаться внедрить иерархическую структуру данных в своем приложении. Но это может быть много усилий. Если он еще не хочет этого делать, он может попробовать линейный подход, который является грубой силой, но, возможно, не так уж плох, если его список не очень большой. Я постараюсь отредактировать приведенный выше код, чтобы добавить в мою 2D-функцию тривиального отклонения ограничивающую рамку. Я не думаю, что я был неправ.
Ciaran
Ссылка на GDnet не работает, но тест канонической сферы очень прост, очень дешев и не разветвляется:inside = (dot(p-p0, p-p0) <= r*r)
Ларс Виклунд,
Вместо этого я вставил код выше. Это выглядит совсем не дешево по сравнению с ограничительной рамкой.
Ciaran
1
@ Чиаран Честно говоря, эта статья кажется очень плохой. Прежде всего, он не выполняет тесты с реалистичными данными, а использует одни и те же значения снова и снова. Не то, что вы встретите в реальном сценарии. И нет, согласно статье, BB быстрее только тогда, когда нет столкновения (например, проверка не проходит при первом ifутверждении). Тоже не очень реалистично. Но, честно говоря, если вы начинаете оптимизировать подобные вещи, то вы определенно начинаете не с того места.
Bummzack
0

Я должен сделать это ответом, потому что у меня нет пунктов, чтобы комментировать или поднять голос. Для 99% людей, которые задают этот вопрос, ограничивающий прямоугольник является решением, описанным Ciaran. На скомпилированном языке он в мгновение ока отвергнет 100 000 ненужных единиц. Есть много накладных расходов, связанных с решениями без грубой силы; с меньшими числами (скажем, ниже 1000) они будут дороже с точки зрения времени обработки, чем проверка методом "грубой силы". И они займут намного больше времени программирования.

Я не уверен, что означает «очень большое число» в вопросе, или что другие люди, ищущие здесь ответы, будут подразумевать под этим. Я подозреваю, что мои числа выше консервативны и могут быть умножены на 10; Я лично весьма предубежден против методов грубой силы и серьезно раздражен тем, насколько хорошо они работают. Но я бы не хотел, чтобы кто-то, скажем, с 10000 единиц тратит время на причудливое решение, когда несколько быстрых строк кода помогут. Они всегда могут прийти в голову позже, если им это нужно.

Кроме того, я хотел бы отметить, что проверка ограничивающей сферы требует умножения, когда ограничивающая рамка не делает. Умножение по своей природе занимает в несколько раз больше времени, чем сложение и сравнение. Обязательно будет некоторая комбинация языка, ОС и аппаратного обеспечения, где проверка сферы будет быстрее, чем проверка коробки, но в большинстве мест и раз проверка коробки должна быть быстрее, даже если сфера отклоняет несколько не относящихся к делу единиц, коробка принимает. (И там, где сфера быстрее, новая версия компилятора / интерпретатора / оптимизатора, скорее всего, изменит это.)

RalphChapin
источник
Хотя в вашем ответе нет ничего плохого, вы не отвечаете на вопрос. Это было специально предложено для подхода "не грубой силы". Также вы, кажется, повторяете то, что уже написал Кьяран, и у нас была длинная дискуссия с комментариями о тестах AABB против круга. Разница в производительности просто не имеет значения. Лучше выберите ограничивающий объем, который подходит большинству ваших кандидатов на столкновение, так как это уменьшит количество фактических узкофазных тестов, что окажет большее влияние на производительность в целом.
bummzack