C'è una grande differenza tra un motore di collisione e un motore fisico. Non fanno la stessa cosa, sebbene il motore fisico si basi generalmente su un motore di collisione.
Il motore di collisione viene quindi suddiviso in due parti: rilevamento delle collisioni e risposta alle collisioni. Quest'ultimo fa generalmente parte del motore fisico. Questo è il motivo per cui i motori di collisione e i motori fisici vengono generalmente inseriti nella stessa libreria.
Il rilevamento delle collisioni si presenta in due forme, discrete e continue. I motori avanzati supportano entrambi, poiché hanno proprietà diverse. In generale, il rilevamento continuo delle collisioni è molto costoso e viene utilizzato solo dove è realmente necessario. La maggior parte delle collisioni e della fisica viene gestita con metodi discreti. In metodi discreti, gli oggetti finiranno per penetrarsi a vicenda e il motore fisico lavorerà per separarli. Quindi il motore in realtà non impedisce a un giocatore di camminare parzialmente attraverso un muro o il pavimento, ma lo risolve solo dopo aver rilevato che il giocatore è parzialmente nel muro / pavimento. Mi concentrerò qui sul rilevamento discreto delle collisioni, poiché è quello che ho più esperienza nell'implementazione da zero.
Rilevazione di collisioni
Il rilevamento delle collisioni è relativamente semplice. Ogni oggetto ha una trasformazione e una forma (possibilmente più forme). Gli approcci ingenui farebbero sì che il motore di collisione esegua un ciclo O (n ^ 2) attraverso tutte le coppie di oggetti e verifichi se vi è sovrapposizione tra le coppie. In approcci più intelligenti ci sono più strutture di dati spaziali (ad esempio, per oggetti statici vs dinamici), una forma di delimitazione per ciascun oggetto e sottomaschere convesse in più parti per ciascun oggetto.
Le strutture di dati spaziali includono elementi come alberi di KD, alberi AABB dinamici, alberi / quadrifogli, alberi di partizionamento dello spazio binario e così via. Ognuno ha i suoi vantaggi e svantaggi, motivo per cui alcuni motori di fascia alta ne utilizzano più di uno. Gli alberi AABB dinamici, ad esempio, sono davvero molto veloci e utili per gestire molti oggetti in movimento, mentre un albero KD può essere più adatto alla geometria a livello statico con cui gli oggetti si scontrano. Ci sono anche altre opzioni.
L'ampia fase utilizza le strutture di dati spaziali e un volume di delimitazione astratto per ciascun oggetto. Un volume di delimitazione è una forma semplice che racchiude l'intero oggetto, generalmente con l'obiettivo di racchiuderlo il più "stretto" possibile pur rimanendo a buon mercato con cui eseguire i test di collisione. Le forme di delimitazione più comuni sono Scatole di confine allineate ad asse, Scatole di confine allineate ad oggetto, Sfere e Capsule. Gli AABB sono generalmente considerati i più veloci e facili (le sfere sono più facili e veloci in alcuni casi, ma molte di quelle strutture di dati spaziali richiederebbero comunque di convertire la sfera in AABB), ma tendono anche a adattarsi a molti oggetti piuttosto male. Le capsule sono popolari nei motori 3D per gestire le collisioni a livello di personaggio. Alcuni motori useranno due forme limitanti,
L'ultima fase del rilevamento delle collisioni è quella di rilevare esattamente dove si interseca la geometria. Questo di solito implica l'uso di una mesh (o di un poligono in 2D), sebbene non sempre. Lo scopo di questa fase è scoprire se gli oggetti si scontrano davvero, se è necessario un livello di dettaglio elevato (diciamo, collisione dei proiettili in uno sparatutto, dove si desidera poter ignorare i colpi che mancano appena), e anche per scoprire esattamente dove gli oggetti si scontrano, il che influenzerà il modo in cui gli oggetti rispondono. Ad esempio, se una scatola è seduta sul bordo di un tavolo, il motore deve sapere in quali punti il tavolo sta spingendo contro la scatola; a seconda della distanza della scatola, la scatola potrebbe iniziare a inclinarsi e cadere.
Generazione del collettore di contatto
Gli algoritmi utilizzati qui includono i famosi algoritmi di affinamento del portale GJK e Minkowski, nonché il test dell'asse di separazione. Poiché gli algoritmi popolari funzionano in genere solo per forme convesse, è necessario suddividere molti oggetti complessi in oggetti secondari convessi ed eseguire test di collisione per ciascuno di essi. Questo è uno dei motivi per cui le mesh semplificate vengono spesso utilizzate per le collisioni, nonché la riduzione dei tempi di elaborazione per l'utilizzo di un numero inferiore di triangoli.
Alcuni di questi algoritmi non solo ti dicono che gli oggetti si sono sicuramente scontrati, ma dove si sono scontrati: quanto si stanno penetrando a vicenda e quali sono i "punti di contatto". Alcuni algoritmi richiedono passaggi aggiuntivi, come il ritaglio poligonale, per ottenere queste informazioni.
Risposta fisica
A questo punto, è stato scoperto un contatto e sono disponibili informazioni sufficienti per consentire al motore fisico di elaborare il contatto. La gestione della fisica può diventare molto complessa. Gli algoritmi più semplici funzionano per alcuni giochi, ma anche qualcosa di apparentemente semplice come mantenere stabile una pila di scatole risulta piuttosto difficile e richiede molto lavoro e hack non ovvi.
Al livello più elementare, il motore fisico farà qualcosa del genere: prenderà gli oggetti in collisione e il loro collettore di contatto e calcolerà le nuove posizioni richieste per separare gli oggetti in collisione. Sposterà gli oggetti in queste nuove posizioni. Calcolerà anche il cambiamento di velocità risultante da questa spinta, combinato con i valori di restituzione (rimbalzo) e attrito. Il motore fisico applicherà anche tutte le altre forze che agiscono sugli oggetti, come la gravità, per calcolare le nuove velocità degli oggetti e quindi (fotogramma successivo) le loro nuove posizioni.
Una risposta fisica più avanzata si complica rapidamente. L'approccio di cui sopra si interromperà in molte situazioni, incluso un oggetto seduto sopra altri due. Trattare ogni coppia da sola provocherà "jitter" e gli oggetti rimbalzeranno molto. La tecnica di base è eseguire una serie di iterazioni di correzione della velocità sulle coppie di oggetti in collisione. Ad esempio, con una casella "A" situata sopra altre due caselle "B" e "C", la collisione AB verrà gestita per prima, facendo sì che la casella A si inclini ulteriormente nella casella C. Quindi la collisione CA viene gestita, sera un po 'fuori dalle caselle, ma tirando A verso il basso e in B. Quindi viene eseguita un'altra iterazione, quindi l'errore AB causato dalla correzione CA viene leggermente risolto, creando un po' più errore nella risposta CA. Che viene gestito quando AC viene nuovamente elaborato. Il numero di iterazioni effettuate non è fisso e non vi è alcun punto in cui diventa "perfetto", ma piuttosto qualunque numero di iterazioni smette di dare risultati significativi. 10 iterazioni sono un tipico primo tentativo, ma ci vogliono modifiche per capire il numero migliore per un motore specifico e le esigenze di un gioco particolare.
Contatta la memorizzazione nella cache
Ci sono altri trucchi che risultano davvero utili (più o meno necessari) quando si ha a che fare con molti tipi di giochi. La memorizzazione nella cache dei contatti è una delle più utili. Con una cache dei contatti, ogni set di oggetti in collisione viene salvato in una tabella di ricerca. Ogni frame, quando viene rilevata una collisione, questa cache viene interrogata per vedere se gli oggetti erano precedentemente in contatto. Se gli oggetti non erano precedentemente in contatto, è possibile generare un evento "nuova collisione". Se gli oggetti erano precedentemente in contatto, le informazioni possono essere utilizzate per fornire una risposta più stabile. Qualsiasi voce nella cache dei contatti che non è stata aggiornata in un frame indica due oggetti che si sono separati e può essere generato un evento "oggetto di separazione". La logica di gioco ha spesso usi per questi eventi.
È anche possibile che la logica di gioco risponda ai nuovi eventi di collisione e li contrassegni come ignorati. Questo è davvero utile per implementare alcune funzionalità comuni nelle piattaforme, come le piattaforme che puoi saltare ma in piedi. Le implementazioni ingenue possono semplicemente ignorare le collisioni che hanno una piattaforma discendente-> normale collisione del personaggio (che indica che la testa del giocatore ha colpito la parte inferiore della piattaforma), ma senza contatto con la cache, questo si romperà se la testa del giocatore sporge attraverso la piattaforma e poi inizia cadere. A quel punto, il contatto normale può finire per puntare verso l'alto, facendo apparire il giocatore attraverso la piattaforma quando non dovrebbe. Con la memorizzazione nella cache dei contatti, il motore può guardare in modo affidabile alla normale collisione iniziale e ignorare tutti gli altri eventi di contatto fino a quando la piattaforma e il giocatore si separano nuovamente.
addormentato
Un'altra tecnica molto utile è quella di contrassegnare gli oggetti come "addormentati" se non vengono interagiti. Gli oggetti dormienti non ottengono aggiornamenti di fisica, non si scontrano con altri oggetti dormienti e, in pratica, restano seduti lì congelati nel tempo fino a quando un altro oggetto non dormiente si scontra con loro.
L'impatto è che tutte le coppie di oggetti in collisione che stanno semplicemente seduti lì senza fare nulla non impiegano alcun tempo di elaborazione. Inoltre, poiché non vi è una quantità costante di minuscole correzioni fisiche, le pile saranno stabili.
Un oggetto è un candidato per dormire quando ha avuto una velocità quasi zero per più di un singolo fotogramma. Nota che l'epsilon che usi per testare questa velocità prossima allo zero sarà probabilmente un po 'più alto del solito epsilon di confronto in virgola mobile, poiché dovresti aspettarti un po' di jitter con oggetti impilati e vuoi che intere pile di oggetti si addormentino se ' stai "abbastanza vicino" per stabile. La soglia richiederà ovviamente modifiche e sperimentazione.
vincoli
L'ultimo pezzo importante di molti motori fisici è il risolutore di vincoli. Lo scopo di un tale sistema è facilitare l'implementazione di cose come molle, motori, assi delle ruote, corpi molli simulati, tessuti, corde e catene e talvolta persino fluidi (sebbene il fluido sia spesso implementato come un sistema completamente diverso).
Anche le basi della risoluzione dei vincoli possono richiedere molta matematica e vanno oltre la mia esperienza in questo argomento. Consiglio di dare un'occhiata all'eccellente serie di articoli sulla fisica di Randy Gaul per una spiegazione più approfondita dell'argomento.