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?
performance
data-structure
efficiency
OCyril
fonte
fonte
Respostas:
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:
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:
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).
fonte
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.
fonte
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.
Comparado com algo assim (em 3D):
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.
fonte
inside = (dot(p-p0, p-p0) <= r*r)
if
declaraçã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.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.)
fonte