Particionamento de espaço quando tudo está se movendo


8

fundo

Juntamente com um amigo, estou trabalhando em um jogo 2D que se passa no espaço. Para torná-lo o mais imersivo e interativo possível, queremos que haja milhares de objetos flutuando livremente, alguns agrupados, outros à deriva no espaço vazio.

Desafio

Para aliviar o mecanismo de renderização e física, precisamos implementar algum tipo de particionamento espacial. Existem dois desafios que temos de superar. O primeiro desafio é que tudo está se movendo para que a reconstrução / atualização da estrutura de dados tenha que ser extremamente barata, pois será necessário realizar todos os quadros. O segundo desafio é a distribuição de objetos, como dito anteriormente, pode haver agrupamentos de objetos juntos e vastos pedaços de espaço vazio. Para piorar ainda mais, não há limites para o espaço.

Tecnologias existentes

Analisei técnicas existentes como BSP-Trees, QuadTrees, kd-Trees e até R-Trees, mas, tanto quanto posso dizer, essas estruturas de dados não se encaixam perfeitamente desde a atualização de muitos objetos que foram movidos para outras células é relativamente caro.

O que eu tentei

Tomei a decisão de que eu precisava de uma estrutura de dados mais voltada para inserção / atualização rápida do que para devolver a menor quantidade possível de ocorrências, dada uma consulta. Para esse efeito, tornei as células implícitas para que cada objeto, dada sua posição, possa calcular em qual célula (s) deve estar. Então eu uso um HashMapque mapeia as coordenadas da célula para um ArrayList(o conteúdo da célula). Isso funciona muito bem, pois não há memória perdida nas células 'vazias' e é fácil calcular quais células inspecionar. No entanto, criar todos esses ArrayLists (pior caso N) é caro e cresce HashMapmuitas vezes (embora isso seja um pouco atenuado, fornecendo uma grande capacidade inicial).

Problema

OK, então isso funciona, mas ainda não é muito rápido. Agora eu posso tentar otimizar micro o código JAVA. No entanto, não estou esperando muito disso, já que o criador de perfil diz que é gasto mais tempo na criação de todos os objetos que eu uso para armazenar as células. Espero que haja outros truques / algoritmos por aí que tornem isso muito mais rápido. Aqui está a aparência da minha estrutura de dados ideal:

  • A prioridade número um é a rápida atualização / reconstrução de toda a estrutura de dados
  • É menos importante dividir os objetos em compartimentos do mesmo tamanho, podemos desenhar alguns objetos extras e fazer algumas verificações adicionais de colisão, se isso significa que a atualização é um pouco mais rápida
  • A memória não é realmente importante (jogo para PC)

"[...] terá que ser feito em todos os quadros." Por quê? Você não pode prever se um objeto sairá da célula em um futuro próximo?
API-Beast

Você não pode pular a atualização de objetos muito distantes do player? Ou pelo menos atualizá-los significativamente com menos frequência?
Liosan

@ Mr.Beast e Liosan, essas duas idéias combinadas podem funcionar, mas os objetos terão que ser capazes de descobrir a si mesmos se algo significativo acontecer (como aumento rápido da velocidade) etc ... Você tem exemplos dessa idéia sendo usados?
Roy T.

O criador de perfil informa que é gasto mais tempo criando o ArrayList ou inicializando os objetos contidos? Não foi possível pré-alocar e agrupar esses objetos?
Fabien

A @Fabien, na verdade, alocar e aumentar o ArrayList é o maior problema, o pooling pode ser uma solução. Gostaria de saber se posso descobrir, por tentativa e erro, qual o tamanho do pool e qual o tamanho das listas de matrizes.
Roy T.

Respostas:


6

A técnica que você está usando é muito semelhante a uma técnica de física computacional chamada dinâmica molecular, onde as trajetórias dos átomos (normalmente agora na faixa de partículas de 100k a 10M) são seguidas com etapas de tempo muito pequenas. O principal problema é que, para calcular a força em uma partícula, é necessário comparar sua posição com a posição de todas as outras partículas, que escalam muito mal (n ao quadrado).

Há um truque que posso sugerir, que exige que você escolha a distância máxima em que as coisas podem interagir. Como ponto de partida, eu começaria com algo como 1/10 da dimensão longa do seu espaço e ajustaria ao gosto (corte mais longo significa mais preciso, mas com mais cálculos).

O método é fazer um loop através de cada partícula (i). (I) obtém uma matriz onde todas as partículas no intervalo de i são adicionadas à matriz. O que você obtém no final é uma matriz 2D, onde a i-ésima entrada é uma matriz da partícula na faixa de i. Para calcular as forças para i, você só precisa verificar as entradas na matriz de i.

A arte deste método é escolher a distância de corte e o preenchimento extra (por exemplo, 20%). O ganho de velocidade é que você só precisa verificar algumas interações para cada partícula e recalcular os vizinhos apenas a cada várias etapas. Eu sugiro escolher uma velocidade um pouco rápida e descobrir quantos passos seriam necessários para atravessar a região "padding". Aumentar o preenchimento (50% ou até 100% do ponto de corte) fornece mais etapas entre o recálculo vizinho, mas torna cada etapa um pouco mais lenta. O equilíbrio disso é uma troca.

Outro truque para calcular as distâncias é trabalhar com d ^ 2 em vez de d, removendo várias chamadas para pow () e sqrt ().

Edit: Difícil encontrar um link ref que não seja super técnico. Este é o único que eu pude encontrar.


Parece uma ideia promissora, vou dar uma olhada nisso com certeza!
Roy T.

2

Sua própria solução parece muito boa se você conseguir construir a estrutura de dados em o (n), então eu diria que a otimização deve ser feita na escolha da estrutura de dados e não no algoritmo.

Eu tenho uma implementação semelhante com algumas diferenças: A estrutura principal de dados é uma matriz de tamanho fixo (como ArrayList), que é a melhor para acesso direto a um elemento. Cada célula da matriz contém uma lista vinculada, a melhor para inserções e a lista de matrizes para fazer loop. Mais tarde, precisaremos excluir elementos da lista vinculada, portanto, para tornar essa operação muito rápida, uma idéia é armazenar em cada elemento da lista um iterador que aponte para si mesmo (você disse que a memória não é um problema, certo?)

Para inicialização, cada "partícula" é inserida no final da lista vinculada que corresponde à célula na matriz que corresponde à sua posição no espaço, supondo que o espaço seja particionado em blocos de tamanho fixo. Portanto, ainda estamos com o (n) complexidade, mas otimizamos tudo usando contêineres mais adequados ao uso.

Cada "partícula" tem uma referência à sua lista vinculada contendo para fornecer acesso rápido aos seus vizinhos.

Em cada quadro, podemos fazer a interação entre cada partícula com sua lista de vizinhos, e eu diria também com os 8 azulejos ao redor para evitar efeitos de limiar perto das bordas do azulejo.

Não há necessidade de recalcular todo o particionamento em cada quadro; só precisamos remover e colocar novamente um item quando ele se mover mais do que uma determinada distância ou, por segurança, todos os quadros X. Uma idéia poderia ser armazenar a posição de cada item no momento em que foi inserido em uma lista vinculada e, em cada quadro, comparar a posição atual com a antiga.



Eu usei algo semelhante a isso no cálculo de dados com base em posições simuladas de átomos. Ele acelerou um cálculo que levou horas / dias e o transformou em minutos. É um pouco complicado de configurar.
Brian Broom
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.