Ao desenvolver um jogo RTS razoavelmente simples, notei que meus cálculos de distância estavam causando um impacto no desempenho.
Em todos os momentos, há verificações de distância para saber se uma unidade está dentro do alcance do alvo, se o projétil atingiu o alvo, se o jogador atropelou uma partida, colisão geral, etc. A lista continua e verifica se a distância entre dois pontos é muito usada.
Minha pergunta é exatamente sobre isso. Quero saber quais alternativas os desenvolvedores de jogos têm para verificar distâncias, além da abordagem usual sqrt (x * x + y * y), que consome bastante tempo se estiver sendo executada milhares de vezes por quadro.
Gostaria de ressaltar que estou ciente das comparações entre distâncias de Manhattan e distâncias quadradas (ignorando o gargalo do sqrt). Algo mais?
Respostas:
TL; DR; Seu problema não está em executar a função de distância. Seu problema está executando a função de distância tantas vezes. Em outras palavras, você precisa de uma otimização algorítmica em vez de matemática.
[EDIT] Estou excluindo a primeira seção da minha resposta, porque as pessoas estão odiando. O título da pergunta pedia funções de distância alternativas antes da edição.
Você está usando uma função de distância na qual calcula a raiz quadrada sempre. No entanto, você pode simplesmente substituí-lo sem usar a raiz quadrada e calcular a distância ao quadrado. Isso economizará muitos ciclos preciosos.esse é realmente um truque comum. Mas você precisa ajustar seus cálculos de acordo. Também pode ser usado como verificação inicial antes de calcular a distância real.Assim, por exemplo, em vez de calcular a distância real entre dois pontos / esferas para um teste de interseção, podemos calcular a Distância ao quadrado e comparar com o raio ao quadrado em vez do raio.Edit, bem depois que @ Byte56 apontou que eu não li a pergunta e que você estava ciente da otimização da distância ao quadrado.
Bem, no seu caso, infelizmente, estamos na computação gráfica lidando quase exclusivamente com o Espaço Euclidiano , e a distância é exatamente definida como
Sqrt of Vector dot itself
no espaço euclidiano.Distância ao quadrado é a melhor aproximação que você obterá (em termos de desempenho), não consigo ver nada superando 2 multiplicações, uma adição e uma tarefa.
Seu problema não está em executar a função de distância. Seu problema está executando a função de distância tantas vezes. Em outras palavras, você precisa de uma otimização algorítmica em vez de matemática.
O ponto é, em vez de verificar a interseção do jogador com cada objeto na cena, cada quadro. Você pode usar facilmente a coerência espacial em seu proveito e verificar apenas os objetos que estão próximos ao player (com maior probabilidade de acertar / cruzar).
Isso pode ser feito facilmente, armazenando essas informações espaciais em uma estrutura de dados de particionamento espacial . Para um jogo simples, sugiro um Grid, porque é basicamente fácil de implementar e se encaixa perfeitamente na cena dinâmica.
Cada célula / caixa contém uma lista de objetos que a caixa delimitadora da grade inclui. E é fácil rastrear a posição do jogador nessas células. E para os cálculos de distância, você só verifica a distância do jogador com esses objetos dentro da mesma célula ou nas células vizinhas, em vez de tudo na cena.
Uma abordagem mais complicada é usar BSP ou Octrees.
fonte
Se você precisar de algo que permaneça linear a qualquer distância (ao contrário
distance^2
) e, no entanto, pareça vagamente circular (ao contrário das distâncias quadradas de Chebyshev e Manhattan, como diamante), você pode calcular a média das duas últimas técnicas para obter uma aproximação de distância em formato octogonal:Aqui está uma visualização (plotagem de contorno) da função, graças ao Wolfram Alpha :
E aqui está um gráfico de sua função de erro quando comparado à distância euclidiana (radianos, somente no primeiro quadrante):
Como você pode ver, o erro varia de 0% nos eixos a aproximadamente + 12% nos lobos. Ao modificar um pouco os coeficientes, podemos reduzi-lo para +/- 4%:
Atualizar
Usando os coeficientes acima, o erro máximo estará dentro de +/- 4%, mas o erro médio ainda será de + 1,3%. Otimizado para erro médio zero, você pode usar:
que gera erros entre -5% e + 3% e um erro médio de + 0,043%
Ao pesquisar na Web por um nome para esse algoritmo, encontrei essa aproximação octogonal semelhante :
Observe que isso é essencialmente equivalente (embora os expoentes sejam diferentes - esses dão um erro de -1,5% a 7,5%, mas podem ser massageados em +/- 4%) porque
max(dx, dy) + min(dx, dy) == dx + dy
. Usando este formulário, as chamadasmin
emax
podem ser fatoradas em favor de:Isso vai ser mais rápido que a minha versão? Quem sabe ... depende do compilador e de como ele otimiza cada um para a plataforma de destino. Meu palpite é que seria muito difícil ver alguma diferença.
fonte
FDIV
,FSQRT
e outras funções transcendentais) custa essencialmente o mesmo que suas versões inteiras: 1 ou 2 ciclos por instrução.Às vezes, essa pergunta pode surgir não pelo custo de realizar cálculos de distância, mas pelo número de vezes que o cálculo está sendo feito.
Em um grande mundo de jogos com muitos atores, não é escalável verificar a distância entre um ator e todos os outros. À medida que mais jogadores, NPCs e projéteis entrar no mundo, o número de comparações que precisam ser feitas irá crescer de forma quadrática com
O(N^2)
.Uma maneira de reduzir esse crescimento é usar uma boa estrutura de dados para descartar rapidamente atores indesejados dos cálculos.
Estamos procurando uma maneira de iterar com eficiência todos os atores que possam estar ao alcance, excluindo a maioria dos atores que estão definitivamente fora do alcance .
Se seus atores estão distribuídos de maneira bastante uniforme pelo espaço mundial, uma grade de baldes deve ser uma estrutura adequada (como sugere a resposta aceita). Mantendo as referências aos atores em uma grade grossa, você precisa apenas verificar alguns dos baldes próximos para cobrir todos os atores que possam estar ao alcance, ignorando o restante. Quando um ator se move, pode ser necessário movê-lo de seu antigo balde para um novo.
Para atores dispersos de maneira menos uniforme, uma quadtree pode se sair melhor em um mundo bidimensional, ou uma octree seria adequada para um mundo tridimensional. Essas são estruturas de propósito mais geral que podem particionar eficientemente grandes áreas de espaço vazio e pequenas áreas contendo muitos atores. Para atores estáticos , existe o BSP ( binary space particitioning), que é muito rápido para pesquisar, mas é muito caro para atualizar em tempo real. Os BSPs separam o espaço usando planos para cortá-lo ao meio repetidamente e podem ser aplicados a qualquer número de dimensões.
É claro que existem custos indiretos para manter os atores com essa estrutura, especialmente quando eles estão se movendo entre as partições. Porém, em um mundo grande, com muitos atores, mas com pequenas faixas de interesse, os custos devem ser bem menores do que os incorridos pela comparação ingênua de todos os objetos.
A consideração de como a despesa de um algoritmo aumenta à medida que recebe mais dados é crucial para o design de software escalável. Às vezes, basta escolher a estrutura de dados correta . Os custos são geralmente descritos utilizando Big O notação .
(Sei que essa não é uma resposta direta à pergunta, mas pode ser útil para alguns leitores. Peço desculpas se perdi seu tempo!)
fonte
Que tal a distância de Chebyshev? Para os pontos p, q, é definido da seguinte forma:
Portanto, para os pontos (2, 4) e (8, 5), a distância de Chebyshev é 6, como | 2-8 | > 4-5 |.
Além disso, seja E a distância euclidiana e C seja a distância de Chebyshev. Então:
O limite superior provavelmente não é muito útil, já que você teria que calcular a raiz quadrada, mas o limite inferior pode ser útil - sempre que a distância Chebyshev for grande o suficiente para ficar fora do alcance, a distância euclidiana também deve ser, poupando você de ter que calculá-lo.
A desvantagem, é claro, é que, se a distância Chebyshev estiver dentro do alcance, você terá que calcular a distância euclidiana de qualquer maneira, perdendo tempo. Apenas uma maneira de descobrir se será uma vitória líquida!
fonte
Uma otimização local muito simples é simplesmente verificar primeiro uma dimensão única.
Isso é :
Portanto, apenas verificar
fabs (x2 - x1)
como primeiro filtro pode proporcionar um ganho apreciável. Quanto dependerá do tamanho do mundo versus os intervalos relevantes.Além disso, você pode usar isso como uma alternativa à estrutura de dados de particionamento espacial.
Se todos os objetos relevantes forem classificados em uma lista na ordem de coordenadas x, os objetos próximos deverão estar próximos na lista. Mesmo que a lista fique fora de ordem devido a não ser totalmente mantida à medida que os objetos se movem, dados limites de velocidade conhecidos, você ainda pode reduzir a seção da lista a ser pesquisada por objetos próximos.
fonte
Esforços foram feitos no passado para otimizar
sqrt
. Embora não se aplique mais às máquinas atuais, aqui está um exemplo do código-fonte do Quake, que usa o número mágico0x5f3759df
:Uma explicação detalhada do que está acontecendo aqui pode ser encontrada na Wikipedia.
Em resumo, são algumas iterações do método de Newton (um algoritmo numérico que melhora iterativamente uma estimativa), com o número mágico usado para fornecer uma estimativa inicial razoável.
Como Travis aponta, esse tipo de otimização não é mais útil nas arquiteturas modernas. E mesmo que fosse, isso só poderia fornecer uma velocidade constante para o seu gargalo, enquanto o redesenho algorítmico pode obter melhores resultados.
fonte