Como encontrar continuamente todas as entidades dentro de um raio de forma eficiente?

14

Eu tenho um número muito grande de entidades (unidades). Em cada etapa, cada unidade precisa conhecer as posições de todas as unidades próximas a ela (a distância é menor que a constante R ). Todas as unidades se movem continuamente. Isso é em 3D.

Em média, haverá 1% da contagem total de unidades próximo a qualquer outra unidade com as restrições especificadas.

Como posso fazer isso de forma eficiente, sem força bruta?

OCyril
fonte
7
Você vai querer algum tipo de sistema de particionamento espacial: en.wikipedia.org/wiki/Space_partitioning
Tétrada

Respostas:

15

Use um dos algoritmos de particionamento de espaço comum, como uma árvore Quadtree, Octree, BSP ou até mesmo um sistema de grade simples. Cada um tem seus próprios prós e contras para cada cenário específico. Você pode ler mais sobre eles nestes livros .

Geralmente (ou pelo que ouvi dizer, não estou muito familiarizado com o raciocínio por trás disso) um Quadtree ou Octree é mais adequado para ambientes externos, enquanto a árvore BSP se encaixa melhor em cenas internas. E a escolha entre usar um Quadtree ou um Octree depende de quão plano é o seu mundo. Se houver pouca variação no eixo Y usando um Octree, seria um desperdício. Um Octree é basicamente um Quadtree com uma dimensão adicional.

Por fim, não desconsidere a simplicidade da solução Grid. Muitas pessoas ignoram que uma grade simples às vezes pode ser suficiente (e ainda mais eficiente) para seus problemas e, em vez disso, passam diretamente para uma solução mais complexa.

O uso de uma grade consiste simplesmente em dividir o mundo em regiões uniformemente espaçadas e armazenar as entidades na região apropriada do mundo. Então, dada uma posição, encontrar as entidades vizinhas seria uma questão de iterar sobre as regiões que cruzam seu raio de pesquisa.

Digamos que seu mundo variou de (-1000, -1000) a (1000, 1000) no plano XZ. Você poderia, por exemplo, dividi-lo em uma grade de 10x10, assim:

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

Em seguida, você colocaria entidades em suas células apropriadas na grade. Por exemplo, uma entidade com XZ (-1000, -1000) cairia na célula (0,0) enquanto uma entidade com XZ (1000, 1000) cairia na célula (9, 9). Depois, dada uma posição e um raio no mundo, você pode determinar quais células são interceptadas por esse "círculo" e iterar apenas sobre elas, com um simples duplo para.

De qualquer forma, pesquise todas as alternativas e escolha a que parece se encaixar melhor no seu jogo. Admito que ainda não tenho conhecimento suficiente sobre o assunto para decidir qual dos algoritmos seria melhor para você.

Editar Encontrei isso em outro fórum e pode ajudá-lo com a decisão:

As grades funcionam melhor quando a grande maioria dos objetos se encaixa dentro de um quadrado de grade e a distribuição é bastante homogênea. Por outro lado, os quadríceps funcionam quando os objetos têm tamanhos variáveis ​​ou estão agrupados em pequenas áreas.

Dada a sua vaga descrição do problema, também estou apoiando a solução da grade (ou seja, assumindo que as unidades são pequenas e distribuídas de maneira bastante homogênea).

David Gouveia
fonte
Obrigado pela resposta detalhada. Sim, parece que a solução Grid simples é boa o suficiente para mim.
OCyril
0

Eu escrevi isso há algum tempo. Agora está em um site comercial, mas você pode obter a fonte para uso pessoal gratuitamente. Pode ser um exagero e está escrito em Java, mas está bem documentado, portanto não deve ser muito difícil aparar e reescrever em outro idioma. Ele basicamente usa um Octree, com ajustes para lidar com objetos muito grandes e com vários threads.

Descobri que a Octree oferecia a melhor combinação de flexibilidade e eficiência. Comecei com uma grade, mas era impossível dimensionar os quadrados corretamente e grandes pedaços de quadrados vazios consumiam espaço e poder de computação por nada. (E isso foi apenas em duas dimensões.) Meu código lida com consultas de vários threads, o que aumenta muito a complexidade, mas a documentação deve ajudá-lo a contornar isso, se você não precisar.

RalphChapin
fonte
0

Para aumentar sua eficiência, tente rejeitar trivialmente os 99% das "unidades" que não estão próximas da unidade de destino usando uma caixa de seleção muito barata. E eu espero que você possa fazer isso sem estruturar seus dados espacialmente. Portanto, se todas as suas unidades estiverem armazenadas em uma estrutura de dados simples, você poderá tentar percorrê-la do início ao fim e primeiro verificar se a unidade atual está fora da caixa delimitadora da unidade de interesse.

Defina uma caixa delimitadora de tamanho grande para a unidade de interesse, de forma que ela possa rejeitar com segurança itens que não têm chance de serem considerados "próximos" dela. A verificação de exclusão de uma caixa delimitadora pode ser mais barata que uma verificação de raio. No entanto, em alguns sistemas em que isso foi testado, não foi o caso. Os dois têm desempenho quase igual. Isso foi editado após muito debate abaixo.

Primeiro: clipe de caixa delimitadora 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;
}

Comparado com algo assim (em 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;
}.

Se o objeto não for trivialmente rejeitado, você realiza um teste de colisão mais caro e preciso. Mas você está apenas procurando proximidade, de modo que o teste de esfera é adequado para isso, mas apenas para 1% dos objetos que sobrevivem à rejeição trivial.

Este artigo suporta a caixa de rejeição trivial. http://www.h3xed.com/programming/bounding-box-vs-bounding-circle-collision-detection-performance-as3

Se essa abordagem linear não fornecer o desempenho de que você precisa, poderá ser necessária uma estrutura hierárquica de dados, como os outros pôsteres. Vale a pena considerar as árvores-R. Eles suportam mudanças dinâmicas. Eles são os BTrees do mundo espacial.

Só não queria que você se desse ao trabalho de introduzir tanta complexidade, se pudesse evitá-la. Além disso, e o custo de manter essa estrutura de dados complexa atualizada à medida que os objetos se movimentam várias vezes por segundo?

Lembre-se de que uma grade é uma estrutura de dados espaciais de um nível. Esse limite significa que não é verdadeiramente escalável. À medida que o mundo cresce, também aumenta o número de células que você precisa cobrir. Eventualmente, esse número de células se torna um problema de desempenho. No entanto, para um determinado mundo de tamanho, ele proporcionará um enorme aumento de desempenho, sem particionamento espacial.

Ciaran
fonte
1
O OP disse especificamente que deseja evitar uma abordagem de força bruta, que é exatamente o que você descreve em seu primeiro parágrafo. Além disso, como você acha que uma verificação de caixa delimitadora é mais barata que uma verificação de esfera delimitadora ?! Isso está errado.
notlesh
Sim, eu sei que ele quer evitar a força bruta que seria evitada se esforçando para introduzir uma estrutura de dados hierárquica em sua aplicação. Mas isso poderia ser muito esforço. Se ele não quiser fazer isso ainda, ele pode tentar a abordagem linear, que é força bruta, mas pode não ter um desempenho tão ruim se sua lista não for muito grande. Vou tentar editar o código acima para colocar na minha função de rejeição trivial da caixa delimitadora 2D. Eu não acho que estava errado.
Ciaran
O link para GDnet está quebrado, mas o teste esfera canônico é muito simples, muito barato e não faz ramo:inside = (dot(p-p0, p-p0) <= r*r)
Lars Viklund
Em vez disso, colei o código acima. Parece tudo menos barato comparado à caixa delimitadora.
Ciaran
1
@ Ciaran Muito honestamente, esse artigo parece muito ruim. Antes de tudo, ele não faz os testes com dados realistas, mas usa os mesmos valores repetidamente. Não é algo que você encontrará em um cenário real. E não, de acordo com o artigo, o BB é mais rápido quando não há colisão (por exemplo, a verificação falha na primeira ifdeclaração). Também não é muito realista. Mas, honestamente, se você está começando a otimizar coisas assim, definitivamente está começando no lugar errado.
bummzack
0

Eu tenho que fazer disso uma resposta, porque não tenho os pontos para comentar ou votar. Para 99% das pessoas que fazem essa pergunta, uma caixa delimitadora é a solução, conforme descrito por Ciaran. Em uma linguagem compilada, ele rejeitará 100.000 unidades irrelevantes em um piscar de olhos. Há muita sobrecarga envolvida em soluções sem força bruta; com números menores (digamos abaixo de 1000), serão mais caros em termos de tempo de processamento do que a verificação de força bruta. E eles levarão muito mais tempo de programação.

Não sei ao certo o que significa "um número muito grande" na pergunta ou o que outras pessoas que procuram respostas aqui querem dizer com isso. Suspeito que meus números acima sejam conservadores e possam ser multiplicados por 10; Pessoalmente, sou bastante preconceituoso com as técnicas de força bruta e estou seriamente irritado com a forma como elas funcionam. Mas eu não gostaria que alguém com, digamos, 10.000 unidades perdesse tempo com uma solução sofisticada quando algumas linhas rápidas de código resolverem o problema. Eles sempre podem gostar mais tarde, se necessário.

Além disso, eu observaria que uma verificação de esfera delimitadora requer multiplicação onde a caixa delimitadora não. A multiplicação, por sua natureza, leva várias vezes o tempo que a adição e a comparação. É provável que haja alguma combinação de idioma, sistema operacional e hardware em que a verificação da esfera será mais rápida do que uma verificação de caixa, mas na maioria dos locais e horários a verificação da caixa deve ser mais rápida, mesmo que a esfera rejeite algumas unidades irrelevantes. caixa aceita. (E onde a esfera é mais rápida, é provável que uma nova versão do compilador / intérprete / otimizador mude isso.)

RalphChapin
fonte
Embora não haja nada de errado com sua resposta, você não está respondendo à pergunta. Foi solicitado especificamente uma abordagem "sem força bruta". Além disso, você parece repetir o que Ciaran já escreveu e tivemos uma longa discussão de comentários sobre a AABB vs. testes de círculo. A diferença de desempenho é simplesmente irrelevante. Escolha melhor um volume delimitador que atenda à maioria dos candidatos a colisão, pois reduzirá a quantidade real de testes em fase estreita .. o que terá um impacto maior no desempenho geral.
bummzack