Ci sono molte ragioni per cui potresti voler creare un motore fisico personalizzato: in primo luogo, imparare e affinare le tue abilità in matematica, fisica e programmazione sono ottimi motivi per tentare un simile progetto; in secondo luogo, un motore fisico personalizzato può affrontare qualsiasi tipo di effetto tecnico che il creatore abbia l'abilità di creare. In questo articolo vorrei fornire una solida introduzione su come creare un motore fisico personalizzato completamente da zero.
La fisica fornisce un mezzo meraviglioso per consentire ad un giocatore di immergersi in un gioco. È logico che la padronanza di un motore fisico sia una risorsa potente per ogni programmatore di cui disponga. Ottimizzazioni e specializzazioni possono essere fatte in qualsiasi momento a causa di una profonda comprensione del funzionamento interno del motore fisico.
Alla fine di questo tutorial, saranno trattati i seguenti argomenti, in due dimensioni:
Ecco una rapida demo:
Nota: Sebbene questo tutorial sia scritto usando C ++, dovresti essere in grado di utilizzare le stesse tecniche e concetti in quasi tutti gli ambienti di sviluppo di giochi.
Questo articolo riguarda una buona quantità di matematica e geometria e, in misura molto minore, la codifica vera e propria. Un paio di prerequisiti per questo articolo sono:
Ci sono parecchi articoli e tutorial su Internet, incluso qui su Tuts +, che copre il rilevamento delle collisioni. Sapendo questo, mi piacerebbe passare attraverso l'argomento molto velocemente in quanto questa sezione non è al centro di questo articolo.
Un parallelepipedo allineato sull'asse (AABB) è una scatola che ha i suoi quattro assi allineati al sistema di coordinate in cui risiede. Ciò significa che è una scatola che non può ruotare ed è sempre squadrata a 90 gradi (di solito allineata con lo schermo). In generale si parla di "bounding box" perché gli AABB sono usati per legare altre forme più complesse.
Un esempio AABB.L'AABB di una forma complessa può essere utilizzato come semplice test per verificare se è possibile incrociare forme più complesse all'interno degli AABB. Tuttavia, nel caso della maggior parte dei giochi, l'AABB viene utilizzato come forma fondamentale e in realtà non associa altro. La struttura del tuo AABB è importante. Esistono diversi modi per rappresentare un AABB, tuttavia questo è il mio preferito:
struct AABB Vec2 min; Vec2 max; ;
Questo modulo consente a un AABB di essere rappresentato da due punti. Il punto min rappresenta i limiti inferiori dell'asse xey e il valore max rappresenta i limiti superiori, in altre parole rappresentano gli angoli in alto a sinistra e in basso a destra. Per stabilire se due forme AABB si intersecano è necessario avere una conoscenza di base del Teorema dell'asse di separazione (SAT).
Ecco un rapido test tratto da Real-Time Collision Detection di Christer Ericson, che utilizza il SAT:
bool AABBvsAABB (AABB a, AABB b) // Esci senza intersezione se trovato separato lungo un asse se (a.max.x < b.min.x or a.min.x > b.max.x) restituisce false se (a.max.y < b.min.y or a.min.y > b.max.y) return false // Nessun asse di separazione trovato, quindi c'è almeno un asse di sovrapposizione return true
Un cerchio è rappresentato da un raggio e un punto. Ecco come dovrebbe essere la struttura del tuo cerchio:
struct Circle float radi Vec position;
Verificare se due cerchi intersecano o meno è molto semplice: prendi i raggi dei due cerchi e aggiungili insieme, quindi controlla se questa somma è maggiore della distanza tra i due cerchi.
Un'importante ottimizzazione da fare qui è eliminare qualsiasi necessità di utilizzare l'operatore della radice quadrata:
float Distance (Vec2 a, Vec2 b) return sqrt ((ax - bx) ^ 2 + (ay - by) ^ 2) bool CirclevsCircleUnoptimized (Circle a, Circle b) float r = a.radius + b.radius ritorno r < Distance( a.position, b.position ) bool CirclevsCircleOptimized( Circle a, Circle b ) float r = a.radius + b.radius r *= r return r < (a.x + b.x)^2 + (a.y + b.y)^2
In generale, la moltiplicazione è un'operazione molto più economica rispetto alla radice quadrata di un valore.
La risoluzione dell'impulso è un tipo particolare di strategia di risoluzione delle collisioni. La risoluzione della collisione è l'atto di prendere due oggetti che si trovano ad intersecare e modificarli in modo tale da non permettere loro di rimanere intersecanti.
In generale, un oggetto all'interno di un motore fisico ha tre principali gradi di libertà (in due dimensioni): movimento nel piano xy e rotazione. In questo articolo limitiamo implicitamente la rotazione e usiamo solo AABB e Circles, quindi l'unico grado di libertà che dobbiamo davvero considerare è il movimento lungo il piano xy.
Risolvendo collisioni rilevate poniamo una restrizione sul movimento in modo tale che gli oggetti non possano rimanere intersecanti tra loro. L'idea alla base della risoluzione dell'impulso è quella di utilizzare un impulso (variazione istantanea della velocità) per separare gli oggetti trovati in collisione. Per fare ciò, la massa, la posizione e la velocità di ogni oggetto devono essere prese in considerazione in qualche modo: vogliamo che gli oggetti di grandi dimensioni entrino in collisione con quelli più piccoli per muoversi un po 'durante la collisione e per inviare gli oggetti piccoli che volano via. Vogliamo anche che oggetti con massa infinita non si muovano affatto.
Semplice esempio di ciò che può ottenere la risoluzione dell'impulso.Per ottenere tali effetti e seguire l'intuizione naturale di come si comportano gli oggetti, useremo corpi rigidi e un bel po 'di matematica. Un corpo rigido è solo una forma definita dall'utente (cioè da te, lo sviluppatore) che è implicitamente definita come non deformabile. Sia gli AABB che i cerchi in questo articolo non sono deformabili e saranno sempre un AABB o un cerchio. Nessuno schiacciamento o stiramento consentito.
Lavorare con corpi rigidi consente di semplificare molto matematica e derivazioni. Questo è il motivo per cui i corpi rigidi sono comunemente usati nelle simulazioni di gioco e perché li useremo in questo articolo.
Supponendo che abbiamo due forme che si intersecano, come si separano effettivamente le due? Supponiamo che il nostro rilevamento delle collisioni ci abbia fornito due importanti informazioni:
Per applicare un impulso a entrambi gli oggetti e spostarli, dobbiamo sapere quale direzione spingerli e quanto. La collisione normale è la direzione in cui verrà applicato l'impulso. La profondità di penetrazione (insieme ad altre cose) determina la quantità di un impulso che verrà utilizzato. Ciò significa che l'unico valore che deve essere risolto è la grandezza del nostro impulso.
Ora facciamo un lungo viaggio per scoprire come possiamo risolvere questa grandezza d'impulso. Inizieremo con i nostri due oggetti che sono stati trovati per essere intersecanti:
Equazione 1\ [V ^ AB = V ^ B - V ^ A \] Nota che per creare un vettore dalla posizione A alla posizione B, devi fare: endpoint - punto di partenza
. \ (V ^ AB \) è la velocità relativa da A a B. Questa equazione dovrebbe essere espressa in termini di collisione normale \ (n \) - cioè, vorremmo sapere la velocità relativa da A a B lungo la direzione della collisione normale:
\ [V ^ AB \ cdot n = (V ^ B - V ^ A) \ cdot n \]
Ora stiamo facendo uso del prodotto dot. Il prodotto punto è semplice; è la somma dei prodotti component-wise:
Equazione 3\ [V_1 = \ begin bmatrix x_1 \\ y_1 \ end bmatrix, V_2 = \ begin bmatrix x_2 \\ y_2 \ end bmatrix \\ V_1 \ cdot V_2 = x_1 * x_2 + y_2 * y_2 \ ]
Il prossimo passo è introdurre ciò che è chiamato il coefficiente di restituzione. La restituzione è un termine che significa elasticità o rimbalzo. Ogni oggetto nel tuo motore fisico avrà una restituzione rappresentata come valore decimale. Tuttavia, durante il calcolo degli impulsi verrà utilizzato solo un valore decimale.
Per decidere quale restituzione utilizzare (indicata da \ (e \) per epsilon), si dovrebbe sempre usare la restituzione più bassa coinvolta nella collisione per risultati intuitivi:
// Dati due oggetti A e B e = min (A.restitution, B.restitution)
Una volta che \ (e \) è stato acquisito possiamo inserirlo nella nostra soluzione di equazione per la grandezza dell'impulso.
La legge di restituzione di Newton afferma quanto segue:
Equazione 4\ [V '= e * V \]
Tutto ciò sta dicendo che la velocità dopo una collisione è uguale alla velocità precedente, moltiplicata per qualche costante. Questa costante rappresenta un "fattore di rimbalzo". Sapendo questo, diventa abbastanza semplice integrare la restituzione nella nostra attuale derivazione:
Equazione 5\ [V ^ AB \ cdot n = -e * (V ^ B - V ^ A) \ cdot n \]
Nota come abbiamo introdotto un segno negativo qui. Nella Legge di Restituzione di Newton, \ (V '\), il vettore risultante dopo il rimbalzo, sta effettivamente andando nella direzione opposta di V. Quindi come rappresentiamo direzioni opposte nella nostra derivazione? Introdurre un segno negativo.
Fin qui tutto bene. Ora dobbiamo essere in grado di esprimere queste velocità sotto l'influenza di un impulso. Ecco una semplice equazione per modificare un vettore con un impulso scalare \ (j \) lungo una direzione specifica \ (n \):
Equazione 6\ [V '= V + j * n \]
Speriamo che l'equazione di cui sopra abbia senso, poiché è molto importante capire. Abbiamo un vettore di unità \ (n \) che rappresenta una direzione. Abbiamo uno scalare \ (j \) che rappresenta quanto a lungo sarà il nostro vettore \ (n \). Quindi aggiungiamo il nostro vettore scalato \ (n \) a \ (V \) per ottenere \ (V '\). Questo è solo aggiungere un vettore a un altro, e possiamo usare questa piccola equazione per applicare un impulso di un vettore a un altro.
C'è ancora un po 'di lavoro da fare qui. Formalmente, un impulso è definito come un cambiamento di quantità di moto. Momentum è massa * velocità
. Sapendo questo, possiamo rappresentare un impulso così come è definito formalmente in questo modo:
\ [Impulse = massa * Velocity \\ Velocity = \ frac Impulse massa \ quindi V '= V + \ frac j * n massa \]
I tre punti in un piccolo triangolo (\ (\ quindi \)) possono essere letti come "quindi". È usato per dimostrare che la cosa in anticipo può essere usata per concludere che qualsiasi cosa accada dopo è vera.I buoni progressi sono stati fatti finora! Tuttavia, dobbiamo essere in grado di esprimere un impulso usando \ (j \) in termini di due oggetti diversi. Durante una collisione con l'oggetto A e B, A verrà spinto nella direzione opposta di B:
Equazione 8\ [V '^ A = V ^ A + \ frac j * n massa ^ A \\ V' ^ B = V ^ B - \ frac j * n massa ^ B \]
Queste due equazioni spingono A lontano da B lungo il vettore di unità di direzione \ (n \) per impulso scalare (grandezza di \ (n \)) \ (j \).
Tutto ciò che ora è necessario è unire le equazioni 8 e 5. La nostra equazione risultante sarà simile a questa:
Equazione 9\ [(V ^ A - V ^ V + \ frac j * n massa ^ A + \ frac j * n massa ^ B) * n = -e * (V ^ B - V ^ A) \ cdot n \\ \ quindi \\ (V ^ A - V ^ V + \ frac j * n massa ^ A + \ frac j * n massa ^ B) * n + e * (V ^ B - V ^ A) \ cdot n = 0 \]
Se ricordi, l'obiettivo originale era quello di isolare la nostra grandezza. Questo perché sappiamo in quale direzione risolvere la collisione (assunta dal rilevamento di collisione), e sono rimasti solo per risolvere l'entità di questa direzione. La grandezza che è sconosciuta nel nostro caso è \ (j \); dobbiamo isolare \ (j \) e risolverlo.
Equazione 10\ [(V ^ B - V ^ A) \ cdot n + j * (\ frac j * n massa ^ A + \ frac j * n massa ^ B) * n + e * ( V ^ B - V ^ A) \ cdot n = 0 \\ \ quindi \\ (1 + e) ((V ^ B - V ^ A) \ cdot n) + j * (\ frac j * n massa ^ A + \ frac j * n massa ^ B) * n = 0 \\ \ quindi \\ j = \ frac - (1 + e) ((V ^ B - V ^ A) \ cdot n) \ frac 1 massa ^ A + \ frac 1 massa ^ B \]
Meno male! È stato un bel po 'di matematica! È tutto finito per ora però. È importante notare che nella versione finale di Equation 10 abbiamo \ (j \) a sinistra (la nostra grandezza) e tutto ciò che è a destra è noto. Questo significa che possiamo scrivere alcune righe di codice da risolvere per il nostro impulso scalare \ (j \). E il ragazzo è il codice molto più leggibile della notazione matematica!
void ResolveCollision (Object A, Object B) // Calcola velocità relativa Vec2 rv = B.velocity - A.velocity // Calcola la velocità relativa in termini di direzione normale float velAlongNormal = DotProduct (rv, normal) // Non risolvere se le velocità si separano se (velAlongNormal> 0) ritorna; // Calcola restitution float e = min (A.restitution, B.restitution) // Calcola impulso scalare float j = - (1 + e) * velAlongNormal j / = 1 / A.mass + 1 / B.mass // Applica impulso Vec2 impulso = j * normale A.velocity - = 1 / A.mass * impulso B.velocity + = 1 / B.mass * impulso
Ci sono alcune cose chiave da notare nell'esempio di codice sopra. La prima cosa è il controllo sulla linea 10, if (VelAlongNormal> 0)
. Questo controllo è molto importante; si assicura di risolvere una collisione solo se gli oggetti si stanno muovendo l'uno verso l'altro.
Se gli oggetti si allontanano l'uno dall'altro, non vogliamo fare nulla. Ciò eviterà che oggetti che non dovrebbero essere considerati in collisione si risolvano l'uno lontano dall'altro. Questo è importante per la creazione di una simulazione che segue l'intuizione umana su ciò che dovrebbe accadere durante l'interazione dell'oggetto.
La seconda cosa da notare è che la massa inversa viene calcolata più volte senza motivo. È meglio memorizzare la massa inversa all'interno di ogni oggetto e calcolarla una volta sola:
A.inv_mass = 1 / A.massMolti motori fisici non memorizzano effettivamente la massa grezza. I motori fisici spesso memorizzano la massa inversa e la massa inversa da sole. Succede solo che la maggior parte della matematica che coinvolge la massa è sotto forma di
1 / massa
. L'ultima cosa da notare è che distribuiamo in modo intelligente il nostro impulso scalare \ (j \) sui due oggetti. Vogliamo che piccoli oggetti rimbalzino da grandi oggetti con una grande porzione di \ (j \), e che i grandi oggetti abbiano le loro velocità modificate da una porzione molto piccola di \ (j \).
Per fare questo potresti fare:
float mass_sum = A.mass + B.mass float ratio = A.mass / mass_sum A.velocity - = ratio * rapporto impulso = B.mass / massa_batteria B.velocity + = rapporto * impulso
È importante rendersi conto che il codice sopra è equivalente al ResolveCollision ()
funzione campione di prima. Come detto prima, le masse inverse sono piuttosto utili in un motore fisico.
Se andiamo avanti e usiamo il codice che abbiamo finora, gli oggetti si incontreranno e rimbalzeranno. È fantastico, anche se cosa succede se uno degli oggetti ha una massa infinita? Bene, abbiamo bisogno di un buon modo per rappresentare la massa infinita all'interno della nostra simulazione.
Suggerisco di usare zero come massa infinita - sebbene se proviamo a calcolare la massa inversa di un oggetto con zero avremo una divisione per zero. La soluzione a questo è eseguire quanto segue quando si calcola la massa inversa:
if (A.mass == 0) A.inv_mass = 0 else A.inv_mass = 1 / A.mass
Un valore pari a zero comporterà calcoli corretti durante la risoluzione dell'impulso. Questo è ancora a posto. Il problema di affondare oggetti sorge quando qualcosa inizia a sprofondare in un altro oggetto a causa della gravità. Forse qualcosa con bassa restituzione colpisce una parete con massa infinita e comincia a sprofondare.
Questo affondamento è dovuto a errori in virgola mobile. Durante ogni calcolo in virgola mobile viene introdotto un piccolo errore in virgola mobile a causa dell'hardware. (Per ulteriori informazioni, Google [Errore in virgola mobile IEEE754].) Nel corso del tempo questo errore si accumula in errore posizionale, causando l'affondamento degli oggetti l'uno nell'altro.
Per correggere questo errore, è necessario tenerne conto. Per correggere questo errore posizionale ti mostrerò un metodo chiamato proiezione lineare. La proiezione lineare riduce la penetrazione di due oggetti di una piccola percentuale e ciò viene eseguito dopo l'applicazione dell'impulso. La correzione posizionale è molto semplice: sposta ogni oggetto lungo la collisione normale \ (n \) di una percentuale della profondità di penetrazione:
void PositionalCorrection (Object A, Object B) const float percent = 0,2 // solitamente dal 20% all'80% Vec2 correction = penetrationDepth / (A.inv_mass + B.inv_mass)) * percent * n A.position - = A.inv_mass * correzione B.posizione + = B.inv_mass * correzione
Si noti che scaliamo il profondità di penetrazione
dalla massa totale del sistema. Questo darà una correzione posizionale proporzionale a quanta massa abbiamo a che fare. Piccoli oggetti spingono via più velocemente di oggetti più pesanti.
C'è un piccolo problema con questa implementazione: se risolviamo sempre il nostro errore posizionale, gli oggetti oscillano avanti e indietro mentre si riposano l'uno sull'altro. Per evitare questo, deve essere dato un po 'di gioco. Eseguiamo la correzione posizionale solo se la penetrazione è superiore ad una soglia arbitraria, indicata come "slop":
void PositionalCorrection (Object A, Object B) const float percent = 0,2 // solitamente dal 20% all'80% const float slop = 0,01 // solitamente da 0,01 a 0,1 Vec2 correction = max (penetration - k_slop, 0.0f) / (A. inv_mass + B.inv_mass)) * percento * n A.posizione - = A.inv_mass * correzione B.posizione + = B.inv_mass * correzione
Ciò consente agli oggetti di penetrare in modo così lieve senza che la correzione della posizione entri in gioco.
L'ultimo argomento da trattare in questo articolo è la semplice generazione multipla. UN molteplice in termini matematici è qualcosa sulla falsariga di "una raccolta di punti che rappresenta un'area nello spazio". Tuttavia, quando mi riferisco al termine collettore, mi riferisco a un piccolo oggetto che contiene informazioni su una collisione tra due oggetti.
Ecco una tipica configurazione multipla:
struct Manifold Object * A; Oggetto * B; penetrazione del galleggiante; Vec2 normale; ;
Durante il rilevamento delle collisioni, è necessario calcolare sia la penetrazione che la collisione normale. Per trovare queste informazioni, è necessario estendere gli algoritmi originali di rilevamento delle collisioni dall'alto di questo articolo.
Iniziamo con l'algoritmo di collisione più semplice: Circle vs Circle. Questo test è per lo più banale. Riesci a immaginare quale sarà la direzione per risolvere la collisione? È il vettore dal cerchio A al cerchio B. Ciò può essere ottenuto sottraendo la posizione di B da quella di A.
La profondità di penetrazione è correlata ai raggi e alla distanza dei cerchi. La sovrapposizione delle cerchie può essere calcolata sottraendo i raggi sommati alla distanza da ciascun oggetto.
Ecco un algoritmo di esempio completo per generare il collettore di una collisione Circle vs. Circle:
bool CirclevsCircle (Manifold * m) // Imposta un paio di puntatori a ciascun oggetto Object * A = m-> A; Oggetto * B = m-> B; // Vettore da A a B Vec2 n = B-> pos - A-> pos float r = A-> raggio + B-> raggio r * = r if (n.LengthSquared ()> r) return false // Cerchi sono entrati in collisione, ora computano il collettore float d = n.Length () // esegue il sqrt reale // Se la distanza tra i cerchi non è zero se (d! = 0) // La distanza è la differenza tra raggio e distanza m-> penetrazione = r - d // Utilizza il nostro d poiché abbiamo eseguito sqrt su di esso già all'interno di Length () // Punti da A a B, ed è un vettore di unità c-> normal = t / d return true // I cerchi sono nella stessa posizione else // Scegli valori casuali (ma coerenti) c-> penetrazione = A-> raggio c-> normal = Vec (1, 0) return true
Le cose più importanti qui sono: non eseguiamo alcuna radice quadrata finché non è necessario (gli oggetti sono in collisione), e controlliamo che i cerchi non siano nella stessa posizione esatta. Se si trovano nella stessa posizione, la nostra distanza sarebbe pari a zero e dobbiamo evitare la divisione per zero quando calcoliamo t / d
.
Il test da AABB a AABB è un po 'più complesso di Circle vs Circle. La collisione normale non sarà il vettore da A a B, ma sarà una faccia normale. Un AABB è una scatola con quattro facce. Ogni faccia ha un normale. Questa normale rappresenta un vettore unitario perpendicolare alla faccia.
Esamina l'equazione generale di una linea in 2D:
\ [ax + by + c = 0 \\ normale = \ begin bmatrix a \\ b \ end bmatrix \]
Nell'equazione di cui sopra, un
e B
sono il vettore normale per una linea e il vettore (a, b)
si presume che sia normalizzato (la lunghezza del vettore è zero). Di nuovo, la collisione normale (la direzione per risolvere la collisione) sarà nella direzione di una delle normali della faccia.
c
rappresenta nell'equazione generale di una linea? c
è la distanza dall'origine. Questo è molto utile per testare se un punto si trova su un lato di una linea o un altro, come vedremo nel prossimo articolo. Ora tutto ciò che serve è scoprire quale faccia è in collisione su uno degli oggetti con l'altro oggetto, e noi abbiamo la nostra normalità. Tuttavia a volte possono intersecarsi più facce di due AABB, ad esempio quando due angoli si intersecano tra loro. Questo significa che dobbiamo trovare il asse di minor penetrazione.
Due assi di penetrazione; l'asse x orizzontale è l'asse di minor penetrazione e questa collisione deve essere risolta lungo l'asse x.Ecco un algoritmo completo per la generazione del collettore e il rilevamento delle collisioni AABB in AABB:
bool AABBvsAABB (Manifold * m) // Imposta un paio di puntatori a ciascun oggetto Object * A = m-> A Object * B = m-> B // Vector da A a B Vec2 n = B-> pos - A- > pos AABB abox = A-> aabb AABB bbox = B-> aabb // Calcola le mezze estensioni lungo l'asse x per ogni oggetto float a_extent = (abox.max.x - abox.min.x) / 2 float b_extent = (bbox .max.x - bbox.min.x) / 2 // Calcola sovrapposizione sull'asse x float x_overlap = a_extent + b_extent - abs (nx) // Test SAT sull'asse x if (x_overlap> 0) // Calcola la metà delle estensioni lungo l'asse x per ogni oggetto float a_extent = (abox.max.y - abox.min.y) / 2 float b_extent = (bbox.max.y - bbox.min.y) / 2 // Calcola sovrapposizione sull'asse y float y_overlap = a_extent + b_extent - abs (ny) // SAT test sull'asse y if (y_overlap> 0) // Scopri quale asse è l'asse di penetrazione minima se (x_overlap> y_overlap) // Punta verso B sapendo che n punti da A a B se (n < 0) m->normal = Vec2 (-1, 0) else m-> normal = Vec2 (0, 0) m-> penetration = x_overlap return true else // Punta verso B sapendo che n punti da A a B if (n.y < 0) m->normal = Vec2 (0, -1) else m-> normal = Vec2 (0, 1) m-> penetration = y_overlap return true
L'ultimo test che esaminerò è il test Circle vs AABB. L'idea qui è di calcolare il punto più vicino sulla AABB al Cerchio; da lì il test si trasforma in qualcosa di simile al test Circle vs Circle. Una volta che il punto più vicino viene calcolato e viene rilevata una collisione, la normale è la direzione del punto più vicino al centro del cerchio. La profondità di penetrazione è la differenza tra la distanza del punto più vicino al cerchio e il raggio del cerchio.
C'è un caso speciale difficile; se il centro del cerchio si trova all'interno dell'AABB, il centro del cerchio deve essere ritagliato sul bordo più vicino dell'AABB e il normale deve essere capovolto.
bool AABBvsCircle (Manifold * m) // Imposta un paio di puntatori a ciascun oggetto Object * A = m-> A Object * B = m-> B // Vector da A a B Vec2 n = B-> pos - A- > pos // Punto più vicino su A al centro di B Vec2 closest = n // Calcola le mezze estensioni lungo ciascun asse float x_extent = (A-> aabb.max.x - A-> aabb.min.x) / 2 float y_extent = (A-> aabb.max.y - A-> aabb.min.y) / 2 // Punto di aggancio ai bordi dell'AABB closest.x = Morsetto (-x_extent, x_extent, closest.x) closest.y = Clamp (-y_extent, y_extent, closest.y) bool inside = false // Circle è all'interno di AABB, quindi dobbiamo bloccare il centro del cerchio // sul bordo più vicino se (n == più vicino) inside = true // Trova l'asse più vicino se (abs (nx)> abs (ny)) // Clamp all'estensione più vicina if (closest.x> 0) closest.x = x_extent else closest.x = -x_extent // L'asse y è più corto // Aggancia all'estensione più vicina se (closest.y> 0) closest.y = y_extent else closest.y = -y_extent Vec2 normale = n - più vicino reale d = normale.LengthSquared () reale r = B-> raggio // Ea la distanza dal raggio è inferiore alla distanza dal punto più vicino e // Cerchio non all'interno di AABB se (d> r * r &&! inside) restituisce falso // Evita sqrt finché non abbiamo avuto bisogno di d = sqrt (d) // Collisione normale deve essere capovolto per puntare all'esterno se il cerchio era // dentro l'AABB se (dentro) m-> normale = -n m-> penetrazione = r - d altro m-> normale = n m-> penetrazione = r - d restituisce vero
Spero che a questo punto tu abbia imparato qualcosa sulla simulazione della fisica. Questo tutorial è sufficiente per impostare un semplice motore fisico personalizzato interamente creato da zero. Nella parte successiva, copriremo tutte le estensioni necessarie richieste da tutti i motori fisici, tra cui:
Spero che questo articolo ti sia piaciuto e non vedo l'ora di rispondere alle domande nei commenti.