Momento e ordem dos problemas de atualização no meu mecanismo de física


22

insira a descrição da imagem aqui

Esta é uma pergunta de "acompanhamento" da minha anterior, sobre detecção e resolução de colisões, que você pode encontrar aqui .


Se você não quiser ler a pergunta anterior, aqui está uma breve descrição de como meu mecanismo de física funciona:

Toda entidade física é armazenada em uma classe chamada SSSPBody.

Apenas AABBs são suportados.

Todo SSSPBody é armazenado em uma classe chamada SSSPWorld, que atualiza todos os corpos e lida com a gravidade.

A cada quadro, o SSSPWorld atualiza todos os corpos.

Todo corpo atualizado procura corpos próximos em um hash espacial, verifica se eles precisam detectar colisões com eles. Se sim, eles invocam um evento de "colisão" e verificam se precisam resolver colisões com eles. Se sim, eles calculam o vetor de penetração e a sobreposição direcional e alteram sua posição para resolver a penetração.

Quando um corpo colide com outro, transfere sua velocidade para o outro simplesmente ajustando a velocidade do corpo à sua.

Um corpo com velocidade é definido como 0 quando não mudou de posição a partir do último quadro. Se também colidir com um corpo em movimento (como um elevador ou uma plataforma em movimento), calcula a diferença de movimento do elevador para ver se o corpo não se moveu da sua última posição.

Além disso, um corpo chama um evento "esmagado" quando todos os seus cantos da AABB se sobrepõem a algo em um quadro.

Este é o código fonte COMPLETO do meu jogo. Está dividido em três projetos. SFMLStart é uma biblioteca simples que trata de entrada, desenho e atualização de entidades. SFMLStartPhysics é o mais importante, onde estão as classes SSSPBody e SSSPWorld. PlatformerPhysicsTest é o projeto do jogo, contendo toda a lógica do jogo.

E este é o método "update" na classe SSSPBody, comentado e simplificado. Você pode dar uma olhada apenas nisso se não estiver com vontade de olhar para todo o projeto SFMLStartSimplePhysics. (E mesmo que você faça isso, você ainda deve dar uma olhada nisso, pois é comentado.)


O gif mostra dois problemas.

  1. Se os corpos são colocados em uma ordem diferente, resultados diferentes acontecem. As caixas à esquerda são idênticas às caixas à direita, colocadas apenas na ordem inversa (no editor).
  2. Ambas as caixas devem ser movidas para o topo da tela. Na situação à esquerda, nenhuma caixa é lançada. À direita, apenas um deles é. Ambas as situações não são intencionais.

Primeiro problema: ordem da atualização

Isso é bastante simples de entender. Na situação à esquerda, a caixa superior é atualizada antes da outra. Mesmo que a caixa no fundo "transfira" a velocidade para a outra, ela precisa esperar o próximo quadro para se mover. Como não se moveu, a velocidade da caixa inferior é definida como 0.

Eu não tenho nenhuma idéia de como consertar isso. Prefiro que a solução não dependa da "classificação" da lista de atualizações, porque sinto que estou fazendo algo errado em todo o design do mecanismo de física.

Como os principais mecanismos de física (Box2D, Bullet, Chipmunk) lidam com a ordem de atualização?


Segundo problema: apenas uma caixa é lançada em direção ao teto

Ainda não entendo por que isso acontece. O que a entidade "mola" faz é definir a velocidade do corpo para -4000 e reposicioná-la sobre a própria mola. Mesmo se eu desativar o código de reposicionamento, o problema ainda ocorre.

Minha idéia é que, quando a caixa inferior colide com a caixa superior, sua velocidade é definida como 0. Não sei por que isso acontece.


Apesar da chance de parecer alguém que desiste do primeiro problema, publiquei todo o código-fonte do projeto acima. Não tenho nada para provar isso, mas acredite, tentei consertar isso, mas não consegui encontrar uma solução e não tenho nenhuma experiência anterior com física e colisões. Estou tentando resolver esses dois problemas há mais de uma semana e agora estou desesperada.

Acho que não consigo encontrar uma solução sozinho sem retirar muitos recursos do jogo (transferência de velocidade e molas, por exemplo).

Muito obrigado pelo tempo gasto lendo esta pergunta e ainda mais se você tentar encontrar uma solução ou sugestão.


Sempre que você empilha caixas, pode combinar a física delas para que elas sejam consideradas um único objeto?
CiscoIPPhone

Respostas:


12

Na verdade, problemas de ordem de atualização são bastante comuns para os motores de física de impulso normais, você não pode simplesmente atrasar a aplicação da força como sugere Vigil, acabaria quebrando a preservação de energia quando um objeto colidir simultaneamente com outros 2. Geralmente eles conseguem fazer algo que parece bastante real, mesmo que uma ordem diferente de atualização tivesse feito um resultado significativamente diferente.

De qualquer forma, para o seu propósito, existem soluços suficientes em um sistema de impulso que eu sugiro que você construa um modelo de mola em massa.

A idéia básica é que, em vez de tentar resolver uma colisão em uma etapa, você aplique uma força aos objetos que colidem, essa força deve ser equivalente à quantidade de sobreposição entre os objetos; isso é comparável ao modo como os objetos reais durante uma colisão transformam seus objetos. movendo a energia para a deformação e, em seguida, de volta ao movimento, a grande vantagem desse sistema é que ele permite que a força viaje através de um objeto sem que esse objeto precise saltar para frente e para trás, e isso pode ser feito de maneira completamente independente da atualização.

Para que os objetos parem, em vez de se movimentarem indefinidamente, você precisará aplicar alguma forma de amortecimento. Você pode afetar bastante o estilo e a sensação do seu jogo, dependendo de como fazê-lo, mas uma abordagem muito básica seria: aplicar uma força a dois objetos tocantes equivalentes ao seu movimento interno; você pode optar por aplicá-la apenas quando eles estiverem se movendo um para o outro, ou também quando eles se afastarem um do outro; este último pode ser usado para impedir completamente que objetos retornem. quando atingem o chão, mas também os tornam um pouco pegajosos.

Você também pode fazer um efeito de atrito travando um objeto na direção perpendicular de uma colisão, a quantidade de frenagem deve ser equivalente à quantidade de sobreposição.

Você pode contornar o conceito de massa com bastante facilidade, fazendo com que todos os objetos tenham a mesma massa, e objetos imóveis funcionarão como ter massa infinita se você simplesmente deixar de acelerá-los.

Algum pseudo-código, apenas no caso de o acima não ser suficientemente claro:

//Presuming that you have done collision checks between two objects and now have  
//numbers for how much they overlap in each direction.
overlapX
overlapY
if(overlapX<overlapY){ //Do collision in direction X
    if(obj1.X>obj2.X){
        swap(obj1,obj2)
    }
    //Spring effect:
    obj1.addXvelocity-=overlapX*0.1 //Constant, the lower this is set the softer the  
                                    //collision will be.
    obj2.addXvelocity+=overlapX*0.1
    //Dampener effect:
    velocityDifference=obj2.Xvelocity-obj1.Xvelocity
    //velocityDifference=min(velocityDifference,0) //Uncomment to only dampen when  
                                                   //objects move towards each other.
    obj1.addXvelocity+=velocityDifference*0.1 //Constant, higher for more dampening.
    obj2.addXvelocity-=velocityDifference*0.1
    //Friction effect:
    if(obj1.Yvelocity>obj2.Yvelocity){
        swap(obj1,obj2)
    }
    friction=overlapX*0.01
    if(2*friction>obj2.Yvelocity-obj1.Yvelocity){
        obj1.addYvelocity+=(obj2.Yvelocity-obj1.Yvelocity)/2
        obj2.addYvelocity-=(obj2.Yvelocity-obj1.Yvelocity)/2
    }
    else{
        obj1.addYvelocity+=friction
        obj2.addYvelocity-=friction
    }
}
else{ //Do collision in direction Y

}

O objetivo das propriedades addXvelocity e addYvelocity é que elas sejam adicionadas à velocidade do objeto depois que todo o tratamento de colisão for concluído.

Editar:
você pode fazer coisas na seguinte ordem, onde cada marcador deve ser executado em todos os elementos antes que o próximo seja executado:

  • Detectar colisões, elas podem ser resolvidas assim que forem detectadas.
  • Adicione os valores de addVelocity aos valores de velocidade, adicione gravidade Yvelocity, redefina os valores de addVelocity para 0, mova os objetos de acordo com sua velocidade.
  • Renderize a cena.

Além disso, percebo que o seguinte pode não estar completamente claro no meu post inicial, sob a influência de objetos de gravidade se sobreporem quando descansarem um sobre o outro, o que sugere que a caixa de colisão deve ser um pouco maior que a representação gráfica para evitar sobreposição. visualmente. Esse problema será menor se a física for executada em uma taxa de atualização mais alta. Eu sugiro que você tente rodar a 120Hz para um compromisso razoável entre o tempo da CPU e a precisão da física.

Edit2:
Fluxo do motor de física muito básico:

  • Colisões e gravidade produzem força / aceleração. acceleration = [Complicated formulas]
  • Força / aceleração são adicionadas à velocidade. velocity += acceleration
  • A velocidade é adicionada à posição. position += velocity

Parece bom, nunca pensei em mola de massa para plataformas. Polegares para cima para algo esclarecedor :)
EnoughTea

Vou tentar implementar isso em algumas horas, quando voltar para casa. Devo mover (posição + = velocidade) os corpos simultaneamente e depois verificar se há colisões ou mover e verificar se há colisões um por um? [Além disso, preciso modificar manualmente a posição para resolver colisões? Ou será que mudar a velocidade resolverá isso?]
Vittorio Romeo

Não tenho muita certeza de como interpretar sua primeira pergunta. A resolução de colisão mudará a velocidade e, portanto, apenas influenciará indiretamente a posição.
Aaaaaaaaaaaa

O fato é que movo entidades definindo manualmente sua velocidade para um determinado valor. Para resolver sobreposições, removo a distância de sobreposição da posição delas. Se eu usar seu método, precisarei mover entidades usando forças ou algo mais? Eu nunca fiz isso antes.
Vittorio Romeo

Tecnicamente, sim, você terá que usar forças, no meu trecho de código, no entanto, é um pouco simplificado com todos os objetos com peso 1 e, portanto, a força é igual à aceleração.
Aaaaaaaaaaaa

14

Bem, obviamente você não é alguém que desiste facilmente, você é um verdadeiro homem de ferro, eu teria jogado minhas mãos no ar muito antes, já que este projeto tem uma forte semelhança com uma floresta de algas :)

Primeiro de tudo, posições e velocidades são definidas em todo o lugar, do ponto de vista do subsistema de física é uma receita para um desastre. Além disso, ao alterar itens integrais por vários subsistemas, crie métodos particulares como "ChangeVelocityByPhysicsEngine", "ChangeVelocityBySpring", "LimitVelocity", "TransferVelocity" ou algo parecido. Ele adicionará a capacidade de verificar as alterações feitas por uma parte específica da lógica e fornecerá um significado adicional a essas alterações de velocidade. Dessa forma, a depuração seria mais fácil.

Primeiro problema.

Sobre a própria pergunta. Agora você está apenas aplicando correções de posição e velocidade "à medida que avançam" em ordem de aparência e lógica do jogo. Isso não funcionará para interações complexas sem codificar cuidadosamente a física de cada coisa complexa. Um mecanismo de física separado não é necessário então.

Para fazer interações complexas sem hacks, é necessário adicionar uma etapa adicional entre a detecção de colisões com base nas posições que foram alteradas pelas velocidades iniciais e as mudanças finais de posições com base na "pós-velocidade". Eu imagino que seria assim:

  • integre a velocidade usando todas as forças que atuam nos corpos (você está aplicando correções de velocidade diretamente agora, deixe os cálculos de velocidade no seu mecanismo de física e use forças para mover as coisas) e use a nova velocidade para integrar posições.
  • detectar colisões, depois restaurar a velocidade e as posições,
  • depois processe colisões (usando impulsos sem atualização imediata da posição, ofc, apenas a velocidade é alterada até a etapa final)
  • integre nova velocidade novamente e processe todas as colisões usando impulsos novamente, exceto que agora as colisões são inelásticas.
  • faça a integração final das posições usando a velocidade resultante.

Coisas adicionais podem surgir, como lidar com empurrões, recusa em empilhar quando o FPS é pequeno ou outras coisas assim, esteja preparado :)

Segundo problema

A velocidade vertical de ambas as caixas de "peso morto" nunca muda de zero. Estranhamente, no loop de atualização do PhysSpring você atribui velocidade, mas no loop de atualização do PhysCrate já é zero. É possível encontrar uma linha onde a velocidade dá errado, mas parei de depurar aqui, pois é a situação "Reap What You Sew". É hora de parar de codificar e começar a repensar tudo quando a depuração se tornar difícil. Mas se chegar a um ponto em que é impossível até mesmo para o autor do código entender o que está acontecendo no código, sua base de códigos já estará morta sem que você perceba :)

Terceiro problema

Eu acho que algo está errado quando você precisa recriar uma parte do Farseer para fazer um simples jogo de plataforma baseado em blocos. Pessoalmente, eu pensaria no seu mecanismo atual como uma tremenda experiência e depois o abandonaria completamente para uma física mais simples e direta baseada em blocos. Ao fazer isso, seria sensato pegar coisas como Debug.Assert e talvez até, oh, o horror, testes de unidade, pois seria possível capturar coisas inesperadas mais cedo.


Gostei da comparação "floresta de algas".
Den

Na verdade, tenho um pouco de vergonha de usar essas palavras, mas senti que, se resultar em uma refatoração ou duas, seria justificável.
EnoughTea

Com apenas um teste em t , nem sempre existe a possibilidade de isso acontecer? Eu imaginaria que você precisaria integrar as velocidades em te verificar as colisões em t + 1 antes de definir as velocidades para 0?
Jonathan Connell

Sim, estamos detectando colisões à frente depois de integrar o estado inicial à frente de t para t + dt usando Runge-Kutta ou algo assim.
EnoughTea

"integre a velocidade usando todas as forças que atuam nos corpos" "deixe os cálculos de velocidade no seu mecanismo de física" - entendo o que você está tentando dizer, mas não tenho idéia de como fazer isso. Existe algum exemplo / artigo que você possa me mostrar?
Vittorio Romeo

7

Quando um corpo colide com outro, transfere sua velocidade para o outro simplesmente ajustando a velocidade do corpo à sua.

Seu problema é que essas são suposições fundamentalmente erradas sobre o movimento; portanto, o que você está recebendo não se parece com o movimento que você conhece.

Quando um corpo colide com outro, o momento é conservado. Pensar nisso como "A acerta B" versus "B acerta A" é aplicar um verbo transitivo a uma situação intransitiva. A e B colidem; o momento resultante deve ser igual ao momento inicial. Ou seja, se A e B são massa igual, agora eles estão viajando com a média de suas velocidades originais.

Provavelmente, você também precisará de alguma correção de colisão e um solucionador iterativo, ou terá problemas de estabilidade. Você provavelmente deve ler algumas das apresentações do GDC de Erin Catto.


2
Eles só obteriam a média da velocidade original se a colisão for completamente inelástica, por exemplo, A e B são pedaços de massa.
Mikael Öhman

"simplesmente ajustando a velocidade do corpo para a sua". São declarações como essa que esclarecem por que não está funcionando. Em geral, eu sempre achei que pessoas inexperientes escrevem sistemas de física sem entender os princípios subjacentes envolvidos. Você nunca 'apenas define a velocidade' ou 'simplesmente ...'. Toda modificação das propriedades de um corpo deve ser uma aplicação direta das leis da dinâmica; incluindo conservação de momento, energia, etc. Sim, sempre haverá fatores de correção para compensar instabilidades, mas em nenhum momento você pode apenas mudar magicamente a velocidade de um corpo.
MrCranky 8/08

É mais fácil assumir corpos inelásticos ao tentar colocar um motor em funcionamento, quanto menos complicado, melhor para a solução de problemas.
Patrick Hughes

4

Eu acho que você fez um esforço realmente nobre, mas parece que há problemas fundamentais na forma como o código está estruturado. Como outros sugeriram, pode ajudar a separar as operações em partes discretas, por exemplo:

  1. Fase ampla : faça um loop em todos os objetos - faça um teste rápido (por exemplo, AABB) para determinar quais objetos podem estar colidindo - descarte aqueles que não estão.
  2. Fase estreita : Faça um loop em todos os objetos em colisão - calcule um vetor de penetração para a colisão (por exemplo, usando SAT).
  3. Resposta à colisão : faça um loop na lista de vetores de colisão - calcule um vetor de força com base na massa e use-o para calcular um vetor de aceleração.
  4. Integração : faça um loop em todos os vetores de aceleração e integre a posição (e rotação, se necessário).
  5. Renderização : faça um loop em todas as posições calculadas e renderize cada objeto.

Ao separar as fases, todos os objetos são atualizados progressivamente em sincronia e você não terá as dependências de pedidos com as quais está lutando atualmente. O código também geralmente se torna mais simples e mais fácil de alterar. Cada uma dessas fases é bastante genérica, e geralmente é possível substituir algoritmos melhores depois que você tiver um sistema em funcionamento.

Dito isto, cada uma dessas partes é uma ciência em si mesma e pode ocupar muito tempo tentando encontrar a solução ideal. Pode ser melhor começar com alguns dos algoritmos mais usados:

  • Detecção de colisão de fase ampla : hash espacial .
  • Detecção de colisão em fase estreita : Para uma física simples dos ladrilhos, você pode aplicar os testes de interseção AABB (Axis Aligned Bounding Box). Para formas mais complicadas, você pode usar o Teorema do Eixo Separador . Qualquer que seja o algoritmo usado, ele deve retornar a direção e a profundidade de uma interseção entre dois objetos (chamado vetor de penetração).
  • Resposta de colisão : use Projeção para resolver a interpenetração.
  • Integração : O integrador é o maior determinante da estabilidade e velocidade do motor. Duas opções populares são a integração Verlet (rápida, porém simples) ou RK4 (precisa, mas lenta). O uso da integração de verlet pode levar a um design extremamente simples, pois a maioria dos comportamentos físicos (salto, rotação) simplesmente funciona sem muito esforço. Uma das melhores referências que eu vi para aprender a integração do RK4 é a série de Glen Fiedler sobre física para jogos .

Um bom (e óbvio) lugar para começar é com as leis do movimento de Newton .


Obrigado pela resposta. Como transfiro a velocidade entre os corpos? Isso acontece durante a fase de integração?
Vittorio Romeo

Em certo sentido, sim. A transferência de velocidade começa com a fase de resposta à colisão. É quando você calcula as forças que atuam nos corpos. A força se traduz em aceleração usando a fórmula aceleração = força / massa. A aceleração é usada na fase de integração para calcular a velocidade, que é então usada para calcular a posição. A precisão da fase de integração determina com que precisão a velocidade (e a posição subsequente) muda ao longo do tempo.
Luke Van Em
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.