Se esta é a primeira vez que você faz essa pergunta, sugiro ler a parte de pré-atualização abaixo, depois essa parte. Aqui está uma síntese do problema, no entanto:
Basicamente, eu tenho um mecanismo de detecção e resolução de colisões com um sistema de particionamento espacial de grade onde os grupos de ordem e colisão são importantes. Um corpo de cada vez deve se mover, detectar colisões e resolver colisões. Se eu mover todos os corpos de uma só vez, gerar possíveis pares de colisão, é obviamente mais rápido, mas a resolução é interrompida porque a ordem de colisão não é respeitada. Se eu mover um corpo por vez, sou forçado a fazer com que os corpos verifiquem colisões, e isso se torna um problema ^ 2. Coloque grupos na mistura e você pode imaginar por que fica muito lento muito rápido com muitos corpos.
Atualização: trabalhei muito nisso, mas não consegui otimizar nada.
Também descobri um grande problema: meu mecanismo depende da ordem de colisão.
Eu tentei uma implementação de geração de pares de colisão exclusiva , que definitivamente acelera muito tudo, mas quebrei a ordem de colisão .
Deixe-me explicar:
no meu design original (sem gerar pares), isso acontece:
- um único corpo se move
- depois que se move, atualiza suas células e obtém os corpos contra os quais colide
- se sobrepuser um corpo ao qual precisa resolver, resolva a colisão
isso significa que, se um corpo se mover e atingir uma parede (ou qualquer outro corpo), apenas o corpo que se moveu resolverá sua colisão e o outro corpo não será afetado.
Esse é o comportamento que desejo .
Entendo que não é comum para motores de física, mas tem muitas vantagens para jogos em estilo retrô .
no design de grade usual (gerando pares únicos), isso acontece:
- todos os corpos se movem
- depois que todos os corpos se moverem, atualize todas as células
- gerar pares de colisão exclusivos
- para cada par, lide com a detecção e resolução de colisões
nesse caso, um movimento simultâneo pode resultar na sobreposição de dois corpos e eles serão resolvidos ao mesmo tempo - isso efetivamente faz com que os corpos "se empurrem" e rompe a estabilidade de colisão com vários corpos
Esse comportamento é comum para mecanismos de física, mas não é aceitável no meu caso .
Eu também encontrei outro problema, que é importante (mesmo que não seja provável que isso aconteça em uma situação do mundo real):
- considerar órgãos dos grupos A, B e W
- A colide e resolve contra W e A
- B colide e resolve contra W e B
- A não faz nada contra B
- B não faz nada contra A
pode haver uma situação em que muitos corpos A e B ocupam a mesma célula - nesse caso, há muita iteração desnecessária entre os corpos que não devem reagir entre si (ou apenas detectar colisão, mas não resolvê-los) .
Para 100 corpos ocupando a mesma célula, são 100 ^ 100 iterações! Isso acontece porque pares únicos não estão sendo gerados - mas eu não posso gerar pares únicos , caso contrário, obteria um comportamento que não desejo.
Existe uma maneira de otimizar esse tipo de mecanismo de colisão?
Estas são as diretrizes que devem ser respeitadas:
A ordem de colisão é extremamente importante!
- Os corpos devem se mover um de cada vez , depois verificar colisões, um de cada vez , e resolver após o movimento, um de cada vez .
Os corpos devem ter 3 conjuntos de bits de grupo
- Grupos : grupos aos quais o corpo pertence
- GroupsToCheck : agrupa o corpo deve detectar colisão contra
- GroupsNoResolve : agrupa o corpo não deve resolver colisões contra
- Pode haver situações em que eu só quero que uma colisão seja detectada, mas não resolvida
Pré-atualização:
Prefácio : sei que otimizar esse gargalo não é uma necessidade - o mecanismo já é muito rápido. Eu, no entanto, por motivos divertidos e educacionais, adoraria encontrar uma maneira de tornar o mecanismo ainda mais rápido.
Estou criando um mecanismo de detecção / resposta de colisão 2D C ++ de uso geral, com ênfase na flexibilidade e velocidade.
Aqui está um diagrama muito básico de sua arquitetura:
Basicamente, a classe principal é World
, que possui (gerencia a memória) de a ResolverBase*
, a SpatialBase*
e a vector<Body*>
.
SpatialBase
é uma classe virtual pura que lida com a detecção de colisão em fase ampla.
ResolverBase
é uma classe virtual pura que lida com a resolução de colisões.
Os corpos comunicar à World::SpatialBase*
com SpatialInfo
objetos, pertencentes aos próprios corpos.
Atualmente, existe uma classe espacial:, Grid : SpatialBase
que é uma grade 2D fixa básica. Tem sua própria classe de informação GridInfo : SpatialInfo
,.
Veja como fica sua arquitetura:
A Grid
classe possui uma matriz 2D de Cell*
. A Cell
classe contém uma coleção de (não pertencente) Body*
: a vector<Body*>
que contém todos os corpos que estão na célula.
GridInfo
os objetos também contêm ponteiros não proprietários para as células em que o corpo está.
Como eu disse anteriormente, o mecanismo é baseado em grupos.
Body::getGroups()
retorna umstd::bitset
de todos os grupos dos quais o corpo faz parte.Body::getGroupsToCheck()
retorna umstd::bitset
de todos os grupos contra os quais o corpo tem que verificar a colisão.
Os corpos podem ocupar mais de uma única célula. O GridInfo sempre armazena ponteiros não proprietários nas células ocupadas.
Depois que um único corpo se move, a detecção de colisão acontece. Suponho que todos os corpos sejam caixas delimitadoras alinhadas ao eixo.
Como a detecção de colisão de fase ampla funciona:
Parte 1: atualização de informações espaciais
Para cada um Body
body
:
- As células ocupadas no canto superior esquerdo e as células ocupadas no canto inferior direito são calculadas.
- Se eles diferirem das células anteriores,
body.gridInfo.cells
são limpos e preenchidos com todas as células que o corpo ocupa (2D para loop da célula superior esquerda para a célula inferior direita).
body
agora é garantido saber quais células ele ocupa.
Parte 2: verificações reais de colisão
Para cada um Body
body
:
body.gridInfo.handleCollisions
é chamado:
void GridInfo::handleCollisions(float mFrameTime)
{
static int paint{-1};
++paint;
for(const auto& c : cells)
for(const auto& b : c->getBodies())
{
if(b->paint == paint) continue;
base.handleCollision(mFrameTime, b);
b->paint = paint;
}
}
void Body::handleCollision(float mFrameTime, Body* mBody)
{
if(mBody == this || !mustCheck(*mBody) || !shape.isOverlapping(mBody->getShape())) return;
auto intersection(getMinIntersection(shape, mBody->getShape()));
onDetection({*mBody, mFrameTime, mBody->getUserData(), intersection});
mBody->onDetection({*this, mFrameTime, userData, -intersection});
if(!resolve || mustIgnoreResolution(*mBody)) return;
bodiesToResolve.push_back(mBody);
}
A colisão é então resolvida para cada corpo em
bodiesToResolve
.É isso aí.
Então, eu tenho tentado otimizar essa detecção de colisão de fase ampla por um bom tempo agora. Toda vez que tento algo diferente da arquitetura / configuração atual, algo não sai como o planejado ou assumo a simulação que mais tarde provou ser falsa.
Minha pergunta é: como posso otimizar a fase ampla do meu mecanismo de colisão ?
Existe algum tipo de otimização mágica de C ++ que pode ser aplicada aqui?
A arquitetura pode ser redesenhada para permitir mais desempenho?
- Implementação real: SSVSCollsion
- Body.h , Body.cpp
- World.h , World.cpp
- Grid.h , Grid.cpp
- Cell.h , Cell.cpp
- GridInfo.h , GridInfo.cpp
Saída do Callgrind para a versão mais recente: http://txtup.co/rLJgz
getBodiesToCheck()
foi chamada 5462334 vezes e ocupou 35,1% de todo o tempo de criação de perfil (tempo de acesso de leitura da instrução)