Cum se creează un motor de fizică 2D personalizat Rezoluția de bază și de impuls

Există multe motive pentru care ați putea dori să creați un motor fizic personalizat: în primul rând, învățarea și aprofundarea abilităților tale în matematică, fizică și programare sunt motive foarte mari pentru a încerca un astfel de proiect; în al doilea rând, un motor fizic personalizat poate aborda orice fel de efect tehnic pe care creatorul are abilitatea de a crea. În acest articol aș dori să ofer o prezentare solidă cu privire la modul de a crea un motor fizic personalizat în întregime de la zero.

Fizica oferă un mijloc minunat de a permite unui jucător să se scufunde în cadrul unui joc. Are sens că o stăpânire a unui motor de fizică ar fi un avantaj puternic pentru orice programator care să le aibă la dispoziție. Optimizările și specializările pot fi făcute oricând datorită unei înțelegeri profunde a funcționării interioare a motorului fizicii.

Până la sfârșitul acestui tutorial vor fi acoperite următoarele subiecte, în două dimensiuni:

  • Detectarea simplă a coliziunii
  • Generare generoasă simplă
  • Rezoluția impulsului

Iată o demonstrație rapidă:

Notă: Deși acest tutorial este scris folosind C ++, ar trebui să puteți folosi aceleași tehnici și concepte în aproape orice mediu de dezvoltare a jocului.


Cerințe preliminare

Acest articol implică o sumă echitabilă de matematică și geometrie și, într-o măsură mult mai mică, o codificare reală. O cerință prealabilă cuplu pentru acest articol sunt:

  • O înțelegere de bază a matematică vectorială simplă
  • Abilitatea de a efectua matematica algebrică

Detectarea coliziunii

Există destul de multe articole și tutoriale pe internet, inclusiv aici pe Tuts +, care acoperă detectarea coliziunilor. Știind acest lucru, aș dori să trec subiectul foarte repede, deoarece această secțiune nu este punctul central al acestui articol.

Axe Aliniate Cutii de Bonding

O casetă de legare aliniată la axă (AABB) este o cutie care are cele patru axe aliniate cu sistemul de coordonate în care se află. Aceasta înseamnă că este o cutie care nu se poate roti și este întotdeauna tăiată la 90 de grade (de obicei aliniată cu ecranul). În general, aceasta este menționată ca o "casetă de legare" deoarece AABB-urile sunt folosite pentru a lega alte forme mai complexe.

Un exemplu AABB.

AABB cu o formă complexă poate fi folosit ca un simplu test pentru a vedea dacă se pot intersecta forme mai complexe în interiorul AABB. Cu toate acestea, în cazul majorității jocurilor, AABB este folosit ca formă fundamentală și, de fapt, nu este legat de altceva. Structura AABB este importantă. Există câteva moduri diferite de a reprezenta un AABB, dar acesta este preferatul meu:

struct AABB Vec2 min; Vec2 max; ;

Această formă permite unui AABB să fie reprezentat de două puncte. Punctul min reprezintă limitele inferioare ale axelor x și y, iar max reprezintă limitele superioare - cu alte cuvinte, ele reprezintă colțurile din stânga sus și din dreapta jos. Pentru a afla dacă două forme AABB se intersectează, va trebui să aveți o înțelegere de bază a teoremei Axei de separare (SAT).

Iată un test rapid efectuat de detecția coliziunilor în timp real de către Christer Ericson, care utilizează SAT:

bool AABBvsAABB (AABB a, AABB b) // Ieșire fără intersecție dacă se găsește separată de-a lungul unei axe dacă (a.max.x < b.min.x or a.min.x > b.max.x) returnează false dacă (a.max.y < b.min.y or a.min.y > b.max.y) returnează false // nu există o axă de separare găsită, prin urmare, există cel puțin o axă de suprapunere return true

cerc

Un cerc este reprezentat de o rază și un punct. Iată ce ar trebui să arate structura dvs. de cerc:

struct Circle float radius Vec position;

Testarea pentru a se intersecta sau nu două cercuri este foarte simplă: luați razele celor două cercuri și adăugați-le împreună, apoi verificați dacă această sumă este mai mare decât distanța dintre cele două cercuri.

O optimizare importantă pentru a face aici este să scapi de orice nevoie de a utiliza operatorul rădăcină pătrată:

flotare distanta (Vec2 a, Vec2 b) return sqrt ((ax - bx) ^ 2 + (ay - by) ^ 2) bool CirclevsCircleUnoptimized (Cercul a, Cercul b) float r = a.radius + b.radius retur 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 

În general, multiplicarea este o operație mult mai ieftină decât luarea rădăcinii pătrate a unei valori.


Impuls Rezoluție

Rezoluția impulsurilor este un tip particular de strategie de rezolvare a coliziunilor. Rezolvarea de coliziune este actul de a lua două obiecte care se găsesc să se intersecteze și să le modifice în așa fel încât să nu le permită să rămână să se intersecteze.

În general, un obiect din cadrul unui motor de fizică are trei grade principale de libertate (în două dimensiuni): mișcarea în planul xy și rotația. În acest articol restricționăm implicit rotația și folosim doar AABB-uri și cercuri, deci singurul grad de libertate pe care trebuie să-l luăm în considerare este mișcarea de-a lungul planului xy.

Prin rezolvarea coliziunilor detectate, plasăm o restricție asupra mișcării astfel încât obiectele să nu se mai intersecteze unul pe altul. Ideea din spatele rezoluției impulsului este de a folosi un impuls (schimbarea instantanee a vitezei) pentru a separa obiectele care se găsesc în coliziune. Pentru a face acest lucru, masa, poziția și viteza fiecărui obiect trebuie luate în considerare într-un fel: vrem obiecte mari care se ciocnesc cu cele mai mici să se miște un pic în timpul coliziunii și să trimită obiectele mici zburând. De asemenea, dorim ca obiectele cu masă infinită să nu se miște deloc.

Exemple simple despre ceea ce poate obține rezoluția impulsului.

Pentru a realiza astfel de efecte și pentru a urmări, împreună cu intuiția naturală a modului în care se comportă obiectele, vom folosi corpuri rigide și un pic echitabil de matematică. Un corp rigid este doar o formă definită de utilizator (adică, de dvs., dezvoltatorul) care este implicit definită ca fiind nedeformabilă. Atât AABB-urile, cât și cercurile din acest articol nu sunt deformabile și vor fi întotdeauna fie AABB, fie Cerc. Nu este permisă nimicirea sau întinderea.

Lucrul cu corpurile rigide permite o mulțime de matematică și derivări care să fie puternic simplificate. Acesta este motivul pentru care corpurile rigide sunt utilizate în mod obișnuit în jocurile de simulare și de ce le vom folosi în acest articol.

Obiectele noastre s-au ciocnit - Acum ce?

Presupunând că avem două forme care se intersectează, cum se separă de fapt cele două? Să presupunem că detectarea coliziunii ne-a furnizat două informații importante:

  • Coliziunea este normală
  • Adâncime de penetrare

Pentru a aplica un impuls ambelor obiecte și a le îndepărta, trebuie să știm în ce direcție să le împingem și cât de mult. Coliziunea normală este direcția în care va fi aplicat impulsul. Adâncimea de penetrare (împreună cu alte lucruri) determină cât de mare va fi un impuls. Aceasta înseamnă că singura valoare care trebuie rezolvată este magnitudinea impulsului nostru.

Acum hai să mergem pe o lungă călătorie pentru a descoperi cum putem rezolva această magnitudine de impuls. Vom începe cu cele două obiecte care s-au dovedit a fi intersectate:

Ecuația 1

\ V ^ AB = V ^ B - V ^ A \] Rețineți că pentru a crea un vector de la poziția A la poziția B, trebuie să faceți: punctul final - punctul de pornire. \ (V ^ AB \) este viteza relativă de la A la B. Această ecuație ar trebui exprimată în termenii coliziunii normale \ (n \) - adică, am dori să cunoaștem viteza relativă de la A la B de-a lungul coliziunii direcția normală:

Ecuația 2

\ [V ^ AB \ cdot n = (V ^ B - V ^ A) \ cdot n \

Acum folosim produsul dot. Produsul dot este simplu; este suma produselor compuse din componente:

Ecuația 3

\ V1 = \ begin bmatrix x_1 \\ y_1 \ end bmatrix, V_2 = \ begin bmatrix x_2 \\ y_2 \ end bmatrix \\ V_1 \ cdot V_2 = ]

Următorul pas este să introducem ceea ce se numește coeficientul de restituire. Restituirea este un termen care înseamnă elasticitate, sau bounciness. Fiecare obiect din motorul dvs. fizic va avea o restituire reprezentată ca valoare zecimal. Cu toate acestea, în timpul calculului impulsului va fi utilizată o singură valoare zecimală.

Pentru a decide ce restituire să folosească (indicată de \ (e \) pentru epsilon), trebuie să utilizați întotdeauna cea mai mică restituire implicată în coliziune pentru rezultate intuitive:

// Având în vedere două obiecte A și B e = min (A.restituție, B.restituție)

Odată ce este dobândită \ (e \), putem să o plasăm în rezolvarea ecuației noastre pentru magnitudinea impulsului.

Legea de restituire a lui Newton prevede următoarele:

Ecuația 4

\ [V '= e * V \]

Toate acestea spun că viteza după o coliziune este egală cu viteza din față, înmulțită cu o constantă. Această constantă reprezintă un "factor de declin". Cunoscând acest lucru, devine destul de simplu să integrăm restituirea în actualele noastre derivări:

Ecuația 5

\ V AB \ cdot n = -e * (V ^ B - V ^ A) \ cdot n \]

Observați cum am introdus aici un semn negativ. În Legea de restituire a lui Newton, \ (V '), vectorul care rezultă după sări, merge de fapt în direcția opusă lui V. Deci, cum reprezentăm direcții opuse în derivarea noastră? Introduceți un semn negativ.

Până acum, bine. Acum trebuie să putem exprima aceste viteze sub influența unui impuls. Iată o ecuație simplă pentru modificarea unui vector de către un anumit scalar de impulsuri \ (j \) de-a lungul unei direcții specifice \ (n \):

Ecuația 6

\ [V '= V + j * n \]

Sperăm că ecuația de mai sus are sens, deoarece este foarte important să înțelegem. Avem un vector unit \ (n \) care reprezintă o direcție. Avem un scalar \ (j \) care reprezintă cât timp vectorul \ (n \) va fi. Apoi adăugăm vectorul nostru scalat \ (n \) la \ (V \) pentru a obține \ (V '\). Aceasta este doar adăugarea unui vector pe altul, și putem folosi această ecuație mică pentru a aplica un impuls de la un vector la altul.

Mai este ceva de făcut aici. Formal, un impuls este definit ca o schimbare a momentului. Momentum este masa * viteza. Cunoscând acest lucru, putem reprezenta un impuls așa cum este definit în mod oficial astfel:

Ecuația 7

\ [Impuls = masă * Velocitate \\ Velocitate = \ frac Impulse mass \ deci V '= V + \ frac j * n mass

Cele trei puncte într-un triunghi mic (\ (\, prin urmare \)) pot fi citite ca "prin urmare". Este folosit pentru a arăta că lucrul în prealabil poate fi folosit pentru a concluziona că ceea ce vine mai departe este adevărat.

S-au înregistrat progrese bune până acum! Cu toate acestea, trebuie să putem exprima un impuls folosind \ (j \) în termeni de două obiecte diferite. În timpul unei coliziuni cu obiectele A și B, A va fi împins în direcția opusă lui B:

Ecuația 8

\ V ^ ^ A = V ^ A + \ frac j * n masa ^ A \\ V ^ ^ B ^

Aceste două ecuații vor împinge A departe de B de-a lungul vectorului unității de direcție \ (n \) prin impulsul scalar (magnitudinea lui \ (n \)) \ (j \).

Tot ceea ce este acum necesar este de a uni Ecuațiile 8 și 5. Ecuația noastră rezultantă va arăta astfel:

Ecuația 9

\ (V ^ A - V ^ V + \ frac j * n masa ^ A + \ frac j * n masa ^ B A) \ cdot n \\ \ prin urmare \\ (V ^ A - V ^ V + \ frac j * n mass ^ A + \ frac j * n mass ^ B * (V ^ B - V ^ A) \ cdot n = 0 \]

Dacă vă amintiți, scopul original a fost acela de a ne izola magnitudinea. Acest lucru se datorează faptului că știm ce direcție pentru a rezolva coliziunea din (presupusă dată de detectarea coliziunii) și au rămas doar pentru a rezolva magnitudinea acestei direcții. Amplitudinea care este necunoscută în cazul nostru este \ (j \); trebuie să izolăm \ (j \) și să rezolvăm pentru el.

Ecuația 10

\ F (V ^ B - V ^ A) \ cdot n + j * (frac j * n mass ^ A + \ frac j * n mass ^ B V ^ B - V ^ A) \ cdot n = 0 \\ \ prin urmare \\ (1 + e) (A) + \ frac j * n mass ^ B) * n = 0 \\ \ cdot n \ frac 1 masa ^ A + \ frac 1 masa ^ B]

Uau! A fost un pic de matematica! Totul se termină deocamdată. Este important să observați că în versiunea finală a Ecuației 10 avem \ (j \) în stânga (magnitudinea noastră) și totul în dreapta este cunoscut. Acest lucru inseamna ca putem scrie cateva linii de cod pentru rezolvarea scalarului impulsului nostru \ (j \). Și băiatul este codul mult mai ușor de citit decât notația matematică!

void ResolveCollision (Obiect A, Obiect B) // Calculați viteza relativă Vec2 rv = B.velocitate - A.velocitate // Calculați viteza relativă în ceea ce privește flotarea direcției normale velAlongNormal = DotProduct (rv, normal) // Nu rezolvați dacă vitezele se separă dacă (velAlongNormal> 0) se întoarce; // Calculați restituția floatului e = min (A.restresia, B.restresia) // Calculați impulsul scalar float j = - (1 + e) ​​* velAlongNormal j / = 1 / A.mass + 1 / B.mass // Aplicați impuls Vec2 impuls = j * normal A.velocitate - = 1 / A.mass * impuls B.velocity + = 1 / B.mass * impulse

Există câteva lucruri cheie pe care trebuie să le notați în proba codului de mai sus. Primul lucru este verificarea liniei 10, dacă (VelAlongNormal> 0). Acest control este foarte important; vă asigură că rezolvați numai o coliziune dacă obiectele se deplasează unul spre celălalt.

Două obiecte se ciocnesc, dar viteza le va separa de următorul cadru. Nu rezolvați acest tip de coliziune.

Dacă obiectele se îndepărtează unul de altul, nu vrem să facem nimic. Acest lucru va împiedica obiectele care nu ar trebui să fie considerate de fapt că se ciocnesc de la rezolvarea una de cealaltă. Acest lucru este important pentru crearea unei simulări care urmează intuiției umane asupra a ceea ce ar trebui să se întâmple în timpul interacțiunii obiectului.

Cel de-al doilea lucru care trebuie notat este că masa inversă este calculată de mai multe ori fără nici un motiv. Cel mai bine este să stocați masa inversă în fiecare obiect și să o calculați o singură dată:

A.inv_mass = 1 / A.mass
Multe motoare de fizică nu stochează masa brută. Motoarele fizice deseori stochează masa inversă și masa inversă singură. Se întâmplă ca majoritatea matematicelor care implică masă să fie sub formă de 1 / masa.

Ultimul lucru pe care trebuie să-l observăm este faptul că distribuim în mod inteligent scalarul impulsului nostru (j) asupra celor două obiecte. Vrem obiecte mici să sări de la obiecte mari cu o mare parte din \ (j \), iar obiectele mari să aibă viteze modificate de o porțiune foarte mică de \ (j \),.

Pentru a face acest lucru, puteți face:

float mass_sum = A.mass + B.mass float ratio = A.mass / mass_sum A.velocitate - = raport * impuls raport = B.mass / mass_sum B.velocity + = raport * impuls

Este important să înțelegeți că codul de mai sus este echivalent cu ResolveCollision () eșantion de la înainte. Așa cum am spus mai înainte, masele inverse sunt destul de utile într-un motor de fizică.

Scufundarea obiectelor

Dacă vom merge mai departe și vom folosi codul pe care îl avem până acum, obiectele se vor arunca între ele și vor sări. Acest lucru este minunat, deși ceea ce se întâmplă dacă unul dintre obiecte are o masă infinită? Ei bine, avem nevoie de o modalitate bună de a reprezenta o masă infinită în simularea noastră.

Vă sugerez să folosiți zero ca masa infinită - deși dacă încercăm să calculam masa inversă a unui obiect cu zero, vom avea o diviziune la zero. Soluționarea acestui lucru este de a face următoarele atunci când se calculează masa inversă:

dacă (A.mass == 0) A.inv_mass = 0 altul A.inv_mass = 1 / A.mass

O valoare de zero va duce la calcule adecvate în timpul rezoluției impulsului. Acest lucru este încă în regulă. Problema obiectelor scufundate apare atunci când ceva începe să se scufunde în alt obiect datorită gravitației. Poate că ceva cu restituire scăzută lovește un zid cu masă infinită și începe să se scufunde.

Această scufundare se datorează unor erori în virgulă mobilă. În timpul calculului fiecărui punct în virgulă mobilă, este introdusă o eroare în virgulă mobilă datorată hardware-ului. (Pentru mai multe informații, Google [IEEE754 error point floating].) În timp, această eroare se acumulează în eroare pozițională, determinând obiecte să se scufunde între ele.

Pentru a corecta această eroare trebuie să fie luată în considerare. Pentru a corecta această eroare pozițională vă voi arăta o metodă numită proiecție liniară. Proiecția liniară reduce penetrarea a două obiecte cu un procent mic, iar acest lucru se efectuează după aplicarea impulsului. Corecția poziționării este foarte simplă: mutați fiecare obiect de-a lungul coliziunii normale \ (n \) cu un procent din adâncimea de penetrare:

void PositionalCorrection (Obiect A, Obiect B) const float procent = 0.2 // de obicei 20% la 80% Vec2 corecție = penetrareDepth / (A.inv_mass + B.inv_mass)) * procente * n A.position - = A.inv_mass * corecție poziția B. + = B.inv_mass * corecție

Rețineți că am scalare penetrationDepth de masa totală a sistemului. Aceasta va oferi o corecție pozițională proporțională cu masa pe care o avem. Obiectele mici împing mai repede decât obiectele mai grele.

Există o mică problemă cu această implementare: dacă rezolvăm mereu eroarea noastră pozițională, atunci obiectele vor biciuiți înainte și înapoi în timp ce se vor odihni unul pe celălalt. Pentru a preveni acest lucru trebuie să se acorde o anumită libertate. Noi efectuăm corecția pozițională numai dacă penetrarea depășește un prag arbitrar, denumit "slop":

void PositionalCorrection (Obiect A, Obiect B) const float procent = 0.2 // de obicei 20% la 80% const float slop = 0.01 // de obicei 0.01-0.1 Vec2 corecție = max (penetrare - k_slop, 0.0f) / (A. inv_mass + B.inv_mass)) * procente * n A. poziție - = A.inv_mass * corecție B. poziție + = B.inv_mass * corecție

Acest lucru permite ca obiectele să pătrundă atât de ușor, fără ca corecția de poziționare să pătrundă.


Generare simplă a galeriilor

Ultimul subiect care trebuie abordat în acest articol este generarea generică simplă. A colector în termeni matematici este ceva de-a lungul liniilor "o colecție de puncte care reprezintă o zonă în spațiu". Totuși, când mă refer la termenul manifold mă refer la un obiect mic care conține informații despre o coliziune între două obiecte.

Iată o configurație tipică variată:

Structură distributivă Obiect * A; Obiect * B; flotarea penetrantă; Vec2 normal; ;

În timpul detectării coliziunii, se va calcula atât penetrarea cât și coliziunea normală. Pentru a găsi aceste informații, algoritmii originali de detectare a coliziunilor din partea de sus a acestui articol trebuie să fie extinse.

Circle vs. Circle

Să începem cu cel mai simplu algoritm de coliziune: Circle vs. Circle. Acest test este în mare parte banal. Vă puteți imagina ce direcție va rezolva coliziunea? Este vectorul de la Cercul A la Cercul B. Acest lucru se poate obține prin scăderea poziției lui B de la A.

Adâncimea de penetrare este legată de razele cercurilor și de distanța dintre ele. Suprapunerea Cercurilor poate fi calculată prin scăderea razei însumate cu distanța de la fiecare obiect.

Aici este un algoritm complet de eșantionare pentru generarea galeriei unei coliziuni Circle vs Circle:

bool CirclevsCircle (Colector * m) // Configurați un cuplu pentru fiecare obiect Obiect * A = m-> A; Obiect * B = m-> B; // Vector de la A la B Vec2 n = B-> pos - A-> pos float r = A-> raza + B-> raza r * = r daca (n.LengthSquared ()> r) (d! = 0) // Distanta este diferenta dintre raza si distanta m-> penetrare = r - d // Utilizați d noastre deoarece am executat sqrt pe el deja în Lungime () // Puncte de la A la B, și este un vector de unitate c-> normal = t / d return true / / Cercuri sunt pe aceeași poziție altceva // Alegeți valorile aleatoare (dar consecvente) c-> penetrare = A-> raza c-> normal = Vec (1, 0) return true

Cele mai notabile lucruri aici sunt: ​​nu realizăm rădăcini pătrate până nu este necesar (obiectele se găsesc ca fiind colizibile) și verificăm să ne asigurăm că cercurile nu se află pe aceeași poziție exactă. Dacă aceștia se află în aceeași poziție, distanța noastră ar fi zero și trebuie să evităm divizarea cu zero când vom calcula t / d.

AABB vs AABB

Testul AABB la AABB este un pic mai complex decât Circle vs. Circle. Coliziunea normală nu va fi vectorul de la A la B, dar va fi o față normală. Un AABB este o cutie cu patru fețe. Fiecare față are un normal. Acest normal reprezintă un vector unic care este perpendicular pe față.

Examinați ecuația generală a unei linii în 2D:

\ [ax + by + c = 0 \\ normal = \ început bmatrix a \\ b \ end bmatrix \]

În ecuația de mai sus, A și b sunt vectorul normal pentru o linie, iar vectorul (a, b) se presupune că este normalizată (lungimea vectorului este zero). Din nou, coliziunea noastră normală (direcția de rezolvare a coliziunii) va fi în direcția unuia dintre normalele feței.

Știi ce c reprezintă în ecuația generală a unei linii? c este distanța de la origine. Acest lucru este foarte util pentru testarea pentru a vedea dacă un punct este pe o parte a unei linii sau alta, după cum veți vedea în următorul articol.

Acum, tot ce este necesar este să aflăm ce față se ciocnește pe unul dintre obiecte cu celălalt obiect și avem normalul nostru. Cu toate acestea, uneori se pot intersecta mai multe fețe ale a două AABB, cum ar fi atunci când două colțuri se intersectează reciproc. Aceasta înseamnă că trebuie să găsim axa celui mai mic penetrare.

Două axe de penetrare; axa orizontală x este axa celui mai mic penetrare și această coliziune trebuie rezolvată de-a lungul axei x.

Aici este un algoritm complet pentru AABB pentru generarea generatoarelor AABB și detectarea coliziunilor:

bool AABBvsAABB (distribuitor * m) // Configurați un cuplu pentru fiecare obiect Obiect * A = m-> A Obiect * B = m-> B // Vector de la A la B Vec2 n = B-> pos - > pos AABB abox = A-> aabb AABB bbox = B-> aabb // Calculați jumătate extensii de-a lungul axei x pentru fiecare float de obiect a_extent = (abox.max.x - abox.min.x) / 2 float b_extent = (bbox .max.x - bbox.min.x) / 2 // Calculați suprapunerea pe axa x float x_overlap = a_extent + b_extent - abs (nx) // Testul SAT pe axa x dacă (x_overlap> 0) // Calculați jumătate extensii de-a lungul axei x pentru fiecare float de obiect a_extent = (abox.max.y - abox.min.y) / 2 float b_extent = (bbox.max.y - bbox.min.y) / 2 // Calculați suprapunerea pe flotarea axei y y_overlap = a_extent + b_extent - abs (ny) // Test SAT pe axa y daca (y_overlap> 0) // Afla care axa este axa celui mai mic penetrare daca (x_overlap> y_overlap) puncte de la A la B dacă (nx < 0) m->normal = Vec2 (-1, 0) altfel m-> normal = Vec2 (0, 0) m-> penetrare = x_overlap return true else // Punct spre B știind că n puncte de la A la B dacă < 0) m->normal = Vec2 (0, -1) altfel m-> normal = Vec2 (0, 1) m-> penetrare = y_overlap return true

Circle vs AABB

Ultimul test pe care îl voi examina este testul Circle vs. AABB. Ideea de aici este de a calcula cel mai apropiat punct de pe AABB la Cerc; de acolo testul devine în ceva similar cu testul Circle vs. Circle. Odată ce se calculează cel mai apropiat punct și se detectează o coliziune, direcția normală este direcția punctului cel mai apropiat de centrul cercului. Adâncimea de penetrare este diferența dintre distanța punctului cel mai apropiat de cerc și raza cercului.


Diagramă AABB la intersecția Circle.

Există un caz special dificil; dacă centrul cercului se află în AABB, atunci centrul cercului trebuie să fie tăiat la marginea cea mai apropiată a AABB, iar normalul trebuie să fie răsturnat.

bool AABBvsCircle (distribuitor * m) // Configurați un cuplu de indicatori pentru fiecare obiect Obiect * A = m-> A Obiect * B = m-> B // Vector de la A la B Vec2 n = B-> pos - > pos // Punctul cel mai apropiat pe A la centrul lui B Vec2 cel mai apropiat = n // Calculați jumătate de extensii de-a lungul fiecărei axe float x_extent = (A-> aabb.max.x - A-> aabb.min.x) / 2 float y_extent = (A-> aabb.max.y - A-> aabb.min.y) / 2 / Punct de fixare la marginile AABB closest.x = Clamp (-x_extent, x_extent, closest.x) closest.y = Clapeta (-y_extent, y_extent, closest.y) bool inside = false // Cercul se află în interiorul AABB, deci trebuie să strângem centrul cercului // la cel mai apropiat margine dacă (n == cel mai apropiat) inside = true // Găsiți cea mai apropiată axă dacă (abs (nx)> abs (ny)) // Strângeți până la cea mai apropiată măsură dacă (closest.x> 0) closest.x = x_extent else closest.x = -x_extent / Clasează la cea mai apropiată măsură în cazul în care (closest.y> 0) closest.y = y_extent alt closest.y = -y_extent Vec2 normal = n - cel mai apropiat real d = normal.LengthSquared () real r = B-> // Ea (d> r * r &&! inside) returnează false // se evită sqrt până când avem nevoie d = sqrt (d) // Collision normal trebuie să fie flipped la punctul exterior dacă cercul a fost // în interiorul AABB dacă (înăuntru) m-> normal = -n m-> penetrare = r - d altceva m-> normal = n m-> penetrare = r - d return true

Concluzie

Sperăm că până acum ați învățat un lucru sau două despre simularea fizică. Acest tutorial este suficient pentru a vă permite să configurați un simplu motor fizic personalizat, realizat în întregime de la zero. În partea următoare, vom acoperi toate extensiile necesare pe care le solicită toate motoarele de fizică, inclusiv:

  • Perechea de sortare și desființarea perechii de contacte
  • Broadphase
  • stratificarea
  • Integrare
  • Timestepping
  • Semnificația intersecției
  • Design modular (materiale, masă și forțe)

Sper că v-ați bucurat de acest articol și aștept cu nerăbdare să răspund la întrebări din comentarii.