Momento e ordine di problemi di aggiornamento nel mio motore fisico


22

inserisci qui la descrizione dell'immagine

Questa domanda è una domanda di "follow-up" della mia precedente, riguardante il rilevamento e la risoluzione delle collisioni, che puoi trovare qui .


Se non vuoi leggere la domanda precedente, ecco una breve descrizione di come funziona il mio motore fisico:

Ogni entità fisica è archiviata in una classe chiamata SSSPBody.

Sono supportati solo AABB.

Ogni SSSPBody è archiviato in una classe chiamata SSSPWorld, che aggiorna ogni corpo e gestisce la gravità.

Ogni frame, SSSPWorld aggiorna ogni corpo.

Ogni corpo aggiornato cerca corpi vicini in un hash spaziale, controlla se devono rilevare collisioni con loro. Se sì, invocano un evento di "collisione" e controllano se devono risolvere le collisioni con loro. Se sì, calcolano il vettore di penetrazione e la sovrapposizione direzionale, quindi cambiano posizione per risolvere la penetrazione.

Quando un corpo si scontra con un altro, trasferisce la sua velocità sull'altro semplicemente impostando la velocità del corpo sulla propria.

La velocità di un corpo è impostata su 0 quando non ha cambiato posizione rispetto all'ultimo fotogramma. Se si scontra anche con un corpo in movimento (come un ascensore o una piattaforma mobile), calcola la differenza di movimento dell'ascensore per vedere se il corpo non si è mosso dalla sua ultima posizione.

Inoltre, un corpo invoca un evento "schiacciato" quando tutti i suoi angoli AABB si sovrappongono a qualcosa in una cornice.

Questo è il codice sorgente COMPLETO del mio gioco. È diviso in tre progetti. SFMLStart è una semplice libreria che gestisce input, disegno e aggiornamento di entità. SFMLStartPhysics è il più importante, dove si trovano le classi SSSPBody e SSSPWorld. PlatformerPhysicsTest è il progetto di gioco, contenente tutta la logica del gioco.

E questo è il metodo "update" nella classe SSSPBody, commentato e semplificato. Puoi dare un'occhiata solo a questo se non hai voglia di guardare l'intero progetto SFMLStartSimplePhysics. (E anche se lo fai, dovresti comunque dare un'occhiata a questo dato che è commentato.)


Il .gif mostra due problemi.

  1. Se i corpi sono posizionati in un ordine diverso, si verificano risultati diversi. Le casse a sinistra sono identiche alle casse a destra, disposte solo nell'ordine inverso (nell'editor).
  2. Entrambe le casse dovrebbero essere spinte verso la parte superiore dello schermo. Nella situazione a sinistra, non vengono azionate casse. A destra, solo uno di questi è. Entrambe le situazioni non sono intenzionali.

Primo problema: ordine di aggiornamento

Questo è abbastanza semplice da capire. Nella situazione a sinistra, la cassa più in alto viene aggiornata prima dell'altra. Anche se la cassa in basso "trasferisce" la velocità all'altra, deve attendere che il fotogramma successivo si sposti. Poiché non si è mosso, la velocità della cassa inferiore è impostata su 0.

Non ho idea di come risolvere questo problema. Preferirei che la soluzione non dipendesse dal "ordinamento" dell'elenco degli aggiornamenti, perché sento che sto facendo qualcosa di sbagliato nell'intero progetto del motore fisico.

In che modo i principali motori fisici (Box2D, Bullet, Chipmunk) gestiscono l'ordine di aggiornamento?


Secondo problema: una sola cassa viene spinta verso il soffitto

Non capisco ancora perché questo accada. Ciò che fa l'entità "molla" è impostare la velocità del corpo a -4000 e riposizionarla sulla parte superiore della molla stessa. Anche se disabilito il codice di riposizionamento, il problema si verifica comunque.

La mia idea è che quando la cassa inferiore si scontra con la cassa superiore, la sua velocità è impostata su 0. Non sono sicuro del perché ciò accada.


Nonostante la possibilità di sembrare qualcuno che si arrende al primo problema, ho pubblicato l'intero codice sorgente del progetto sopra. Non ho nulla per dimostrarlo, ma credetemi, ho cercato di risolverlo ma non riuscivo a trovare una soluzione e non ho alcuna esperienza precedente con la fisica e le collisioni. Ho cercato di risolvere questi due problemi per più di una settimana e ora sono disperato.

Non credo di poter trovare una soluzione da solo senza togliere molte funzionalità dal gioco (trasferimento della velocità e molle, per esempio).

Grazie mille per il tempo dedicato a leggere questa domanda e grazie ancora di più se provi persino a trovare una soluzione o un suggerimento.


Ogni volta che impilate le scatole potreste combinare la loro fisica in modo che siano considerati un singolo oggetto?
CiscoIPPhone l'

Risposte:


12

In realtà, i problemi di ordine di aggiornamento sono abbastanza comuni per i normali motori della fisica degli impulsi, non puoi semplicemente ritardare l'applicazione della forza come suggerisce Vigil, finiresti per rompere la conservazione dell'energia quando un oggetto si scontra contemporaneamente con altri 2. Di solito però riescono a creare qualcosa che sembra piuttosto reale, anche se un diverso ordine di aggiornamento avrebbe prodotto un risultato significativamente diverso.

In ogni caso, per il tuo scopo ci sono abbastanza singhiozzi in un sistema di impulsi che ti suggerirei invece di costruire un modello a molla di massa.

L'idea di base è che invece di provare a risolvere una collisione in un solo passaggio si applica una forza agli oggetti in collisione, questa forza dovrebbe essere equivalente alla quantità di sovrapposizione tra gli oggetti, questo è paragonabile a come gli oggetti reali durante una collisione trasformano il loro energia di movimento in deformazione e poi di nuovo in movimento, la cosa grandiosa di questo sistema è che permette alla forza di viaggiare attraverso un oggetto senza che quell'oggetto debba rimbalzare avanti e indietro, e può ragionevolmente essere fatto completamente in ordine di aggiornamento indipendente.

Affinché gli oggetti si fermino piuttosto che rimbalzare indefinitamente dovrai applicare una qualche forma di smorzamento, puoi influenzare notevolmente lo stile e la sensazione del tuo gioco a seconda di come lo fai, ma un approccio molto semplice sarebbe quello di applica una forza a due oggetti toccanti equivalenti al loro movimento interno, puoi scegliere di applicarlo solo quando si muovono l'uno verso l'altro o anche quando si allontanano l'uno dall'altro, quest'ultimo può essere usato per impedire completamente agli oggetti di rimbalzare indietro quando colpiscono il suolo, ma li rendono anche un po 'appiccicosi.

Puoi anche creare un effetto di attrito frenando un oggetto nella direzione perpendicolare di una collisione, la quantità di frenata dovrebbe essere equivalente alla quantità di sovrapposizione.

Potresti aggirare il concetto di massa abbastanza facilmente facendo sì che tutti gli oggetti abbiano la stessa massa e gli oggetti immobili funzioneranno come se avessero massa infinita se trascurassi semplicemente di accelerarli.

Alcuni pseudo-codici, nel caso in cui quanto sopra non fosse abbastanza chiaro:

//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

}

Il punto delle proprietà addXvelocity e addYvelocity è che queste vengono aggiunte alla velocità del loro oggetto dopo aver eseguito tutta la gestione delle collisioni.

Modifica:
potresti fare cose nel seguente ordine, dove ogni proiettile deve essere eseguito su tutti gli elementi prima che venga eseguito il successivo:

  • Rileva collisioni, possono essere risolte non appena vengono rilevate.
  • Aggiungi i valori addVelocity ai valori di velocità, aggiungi gravità Yvelocity, resetta i valori addVelocity su 0, sposta gli oggetti in base alla loro velocità.
  • Rendi la scena.

Inoltre, mi rendo conto che quanto segue potrebbe non essere completamente chiaro nel mio post iniziale, sotto l'influenza degli oggetti di gravità si sovrapporranno quando si riposano uno sopra l'altro, questo suggerisce che la loro scatola di collisione dovrebbe essere leggermente più alta della loro rappresentazione grafica per evitare la sovrapposizione visivamente. Questo problema sarà minore se la fisica viene eseguita con una frequenza di aggiornamento più elevata. Ti suggerisco di provare a correre a 120Hz per un ragionevole compromesso tra tempo della CPU e precisione fisica.

Edit2:
flusso del motore fisico di base:

  • Collisioni e gravità producono forza / accelerazione. acceleration = [Complicated formulas]
  • Forza / accelerazione vengono aggiunte alla velocità. velocity += acceleration
  • La velocità viene aggiunta alla posizione. position += velocity

Sembra buono, non ho mai pensato alla molla di massa per i platform.
Complimenti

Proverò a implementarlo tra qualche ora, quando torno a casa. Devo spostare i corpi (Posizione + = Velocità) contemporaneamente, quindi controllare la presenza di collisioni o spostare e controllare le collisioni una per una? [Inoltre, devo modificare manualmente la posizione per risolvere le collisioni? O ci penserà noi a cambiare la velocità?]
Vittorio Romeo,

Non sono del tutto sicuro di come interpretare la tua prima domanda. La risoluzione della collisione cambierà la velocità e quindi influenzerà solo indirettamente la posizione.
aaaaaaaaaaaa,

Il fatto è che sposto le entità impostando manualmente la loro velocità su un certo valore. Per risolvere le sovrapposizioni, rimuovo la distanza di sovrapposizione dalla loro posizione. Se uso il tuo metodo, dovrò muovere le entità usando forze o qualcos'altro? Non l'ho mai fatto prima.
Vittorio Romeo,

Tecnicamente sì, dovrai usare le forze, nel mio pezzo di codice è comunque un po 'semplificato con tutti gli oggetti con peso 1, e la forza è quindi uguale all'accelerazione.
aaaaaaaaaaaa,

14

Beh, ovviamente non sei una persona che si arrende facilmente, sei un vero uomo di ferro, avrei lanciato le mani in aria molto prima, poiché questo progetto assomiglia molto a una foresta di alghe :)

Innanzitutto, le posizioni e le velocità sono impostate ovunque, dal punto di vista del sottosistema di fisica è una ricetta per un disastro. Inoltre, quando si cambiano le cose integrali da vari sottosistemi, creare metodi privati ​​come "ChangeVelocityByPhysicsEngine", "ChangeVelocityBySpring", "LimitVelocity", "TransferVelocity" o qualcosa del genere. Aggiungerà la capacità di controllare le modifiche apportate da una parte specifica della logica e fornirà un significato aggiuntivo a queste variazioni di velocità. In questo modo il debug sarebbe più semplice.

Primo problema

Sulla domanda stessa. Ora stai solo applicando le correzioni di posizione e velocità "come vanno" in ordine di apparenza e logica di gioco. Ciò non funzionerà per interazioni complesse senza codificare attentamente la fisica di ogni cosa complessa. Quindi non è necessario un motore fisico separato.

Al fine di eseguire interazioni complesse senza hack, è necessario aggiungere un ulteriore passaggio tra il rilevamento di collisioni in base alle posizioni che sono state modificate dalle velocità iniziali e i cambiamenti finali delle posizioni in base alla "velocità successiva". Immagino che sarebbe andata così:

  • integra la velocità usando tutte le forze che agiscono sui corpi (stai applicando le correzioni della velocità direttamente ora, lascia i calcoli della velocità al tuo motore fisico e usa le forze per muovere le cose) , quindi usa la nuova velocità per integrare le posizioni.
  • rilevare collisioni, quindi ripristinare velocità e posizioni,
  • quindi elaborare le collisioni (utilizzando gli impulsi senza aggiornamento immediato della posizione, ofc, solo la velocità viene modificata fino al passaggio finale)
  • integrare di nuovo la nuova velocità ed elaborare di nuovo tutte le collisioni utilizzando nuovamente gli impulsi, tranne per il fatto che ora le collisioni non sono elastiche.
  • effettuare l'integrazione finale delle posizioni usando la velocità risultante.

Potrebbero comparire altre cose, come affrontare il cretino, il rifiuto di impilare quando FPS è piccolo, o altre cose del genere, essere preparati :)

Secondo problema

La velocità verticale di entrambe le casse "a peso morto" non cambia mai da zero. Stranamente, nel ciclo di aggiornamento di PhysSpring si assegna la velocità, ma nel ciclo di aggiornamento di PhysCrate è già zero. È possibile trovare una linea in cui la velocità non va, ma ho smesso di eseguire il debug qui poiché è la situazione "Reap What You Sew". È tempo di smettere di scrivere codice e iniziare a ripensare tutto quando il debug diventa difficile. Ma se arriva a un punto in cui è impossibile anche per l'autore del codice capire cosa sta succedendo nel codice, allora la tua base di codice è già morta senza che te ne accorga :)

Terzo problema

Penso che qualcosa sia spento quando è necessario ricreare una parte di Farseer per fare un semplice platform basato su piastrelle. Personalmente, penserei al tuo attuale motore come a un'esperienza straordinaria, e poi lo abbandonerei completamente per una fisica basata su piastrelle più semplice e diretta. Mentre lo fa, sarebbe saggio raccogliere cose come Debug.Assert e forse anche, oh l'orrore, unit test, dal momento che sarebbe possibile catturare cose inaspettate prima.


Mi è piaciuto quel confronto "foresta di alghe".
Den

In realtà, mi vergogno un po 'di usare queste parole, ma ho sentito che se si traduce in un refactoring o due, allora sarebbe giustificato.
Basta l'

Con un solo test at non ci sarà sempre la possibilità che ciò accada? Immagino che avresti bisogno di integrare le velocità a t e quindi verificare la presenza di collisioni in t + 1 prima di impostare qualsiasi velocità a 0?
Jonathan Connell,

Sì, stiamo rilevando le collisioni in anticipo dopo aver integrato lo stato iniziale in anticipo da t a t + dt usando Runge-Kutta o qualcosa del genere.
Basta l'

"integra la velocità usando tutte le forze che agiscono sui corpi" "lascia i calcoli della velocità al tuo motore fisico" - Capisco cosa stai cercando di dire, ma non ho idea di come farlo. C'è qualche esempio / articolo che puoi mostrarmi?
Vittorio Romeo,

7

Quando un corpo si scontra con un altro, trasferisce la sua velocità sull'altro semplicemente impostando la velocità del corpo sulla propria.

Il tuo problema è che si tratta di ipotesi fondamentalmente errate sul movimento, quindi ciò che stai ottenendo non assomiglia al movimento perché ne hai familiarità.

Quando un corpo si scontra con un altro, lo slancio viene conservato. Pensare a questo come "A colpisce B" contro "B colpisce A" significa applicare un verbo transitivo a una situazione intransitiva. A e B si scontrano; il momento risultante deve essere uguale al momento iniziale. Cioè, se A e B sono pari massa, ora entrambi viaggiano con la media delle loro velocità originali.

Probabilmente avrai anche bisogno di un po 'di pendenza di collisione e di un solutore iterativo, oppure ti imbatterai in problemi di stabilità. Probabilmente dovresti leggere alcune delle presentazioni GDC di Erin Catto.


2
Otterrebbero la media della velocità originale solo se la collisione è completamente anelastica, ad esempio A e B sono pezzi di pasta.
Mikael Öhman,

"semplicemente impostando la velocità del corpo al proprio". Sono dichiarazioni come questa che illuminano il motivo per cui non funziona. In generale ho sempre scoperto che le persone inesperte scrivono sistemi fisici senza comprendere i principi sottostanti coinvolti. Non hai mai "solo impostato la velocità", o "semplicemente ...". Ogni modifica delle proprietà di un corpo dovrebbe essere un'applicazione diretta delle leggi della dinamica; inclusa la conservazione di quantità di moto, energia, ecc. Sì, ci saranno sempre fattori di confusione per compensare le instabilità, ma in nessun momento puoi semplicemente cambiare magicamente la velocità di un corpo.
MrCranky,

È più facile assumere corpi anelastici quando si cerca di far funzionare un motore in primo luogo, meno è complicato meglio è per la risoluzione dei problemi.
Patrick Hughes,

4

Penso che tu abbia fatto uno sforzo davvero nobile, ma sembra che ci siano problemi fondamentali con la struttura del codice. Come altri hanno suggerito, può essere utile separare le operazioni in parti discrete, ad esempio:

  1. Fase larga : scorrere tutti gli oggetti - eseguire un test rapido (ad es. AABB) per determinare quali oggetti potrebbero essere in collisione - scartare quelli che non lo sono.
  2. Fase stretta : attraversa tutti gli oggetti in collisione - calcola un vettore di penetrazione per la collisione (ad es. Usando SAT).
  3. Risposta alla collisione : scorrere l'elenco dei vettori di collisione: calcolare un vettore di forza basato sulla massa, quindi utilizzarlo per calcolare un vettore di accelerazione.
  4. Integrazione : scorrere tutti i vettori di accelerazione e integrare la posizione (e la rotazione, se necessario).
  5. Rendering : scorre tutte le posizioni calcolate e visualizza ciascun oggetto.

Separando le fasi tutti gli oggetti vengono aggiornati progressivamente in sincronia e non si avranno le dipendenze dell'ordine con cui si sta attualmente lottando. Inoltre, il codice risulta generalmente più semplice e facile da modificare. Ognuna di queste fasi è abbastanza generica ed è spesso possibile sostituire algoritmi migliori dopo avere un sistema funzionante.

Detto questo, ognuna di queste parti è una scienza in sé e può impiegare molto tempo a cercare la soluzione ottimale. Potrebbe essere meglio iniziare con alcuni degli algoritmi più comunemente usati:

  • Rilevamento di collisioni a fase ampia : hash spaziale .
  • Rilevamento di collisioni a fase stretta : per una semplice fisica delle piastrelle, è sufficiente applicare i test di intersezione Axis Aligned Bounding Box (AABB). Per forme più complicate puoi usare il Teorema degli assi di separazione . Qualunque algoritmo si usi, dovrebbe restituire la direzione e la profondità di un'intersezione tra due oggetti (chiamato vettore di penetrazione).
  • Risposta alla collisione : utilizzare la proiezione per risolvere l'inter-penetrazione.
  • Integrazione : l'integratore è il principale fattore determinante della stabilità e della velocità del motore. Due opzioni popolari sono l' integrazione Verlet (veloce ma semplice) o RK4 (accurata ma lenta). L'uso dell'integrazione dei verlet può portare a un design estremamente semplice poiché la maggior parte dei comportamenti fisici (rimbalzo, rotazione) funzionano senza troppi sforzi. Uno dei migliori riferimenti che ho visto per aver appreso l'integrazione di RK4 è la serie di Glen Fiedler sulla fisica dei giochi .

Un buon (e ovvio) punto di partenza è con le leggi del moto di Newton .


Grazie per la risposta. Come posso trasferire la velocità tra i corpi, però? Succede durante la fase di integrazione?
Vittorio Romeo,

In un certo senso sì. Il trasferimento di velocità inizia con la fase di risposta alla collisione. Questo è quando calcoli le forze che agiscono sui corpi. La forza si traduce in accelerazione usando la formula accelerazione = forza / massa. L'accelerazione viene utilizzata nella fase di integrazione per calcolare la velocità, che viene quindi utilizzata per calcolare la posizione. L'accuratezza della fase di integrazione determina la precisione con cui la velocità (e successivamente la posizione) cambia nel tempo.
Luke Van Nel
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.