Slik oppretter du en tilpasset 2D-fysikkmotor Grunnleggende og impulsoppløsning

Det er mange grunner til at du kanskje vil lage en egendefinert fysikkmotor: Først lærer du og henter dine ferdigheter i matematikk, fysikk og programmering er gode grunner til å prøve et slikt prosjekt; For det andre kan en tilpasset fysikkmotor takle noen form for teknisk effekt som skaperen har evnen til å skape. I denne artikkelen vil jeg gjerne gi en solid introduksjon om hvordan du oppretter en tilpasset fysikkmotor helt fra bunnen av.

Fysikk gir en flott måte å tillate en spiller å fordype seg i et spill. Det er fornuftig at en beherskelse av en fysikkmotor ville være en kraftig ressurs for enhver programmør å ha til rådighet. Optimaliseringer og spesialiseringer kan til enhver tid gjøres på grunn av en dyp forståelse av fysikkmotorens indre arbeid.

Ved slutten av denne opplæringen har følgende emner blitt dekket, i to dimensjoner:

  • Enkel kollisjon gjenkjenning
  • Enkel mangfoldig generasjon
  • Impulsoppløsning

Her er en rask demo:

Merk: Selv om denne opplæringen er skrevet med C ++, bør du kunne bruke de samme teknikkene og konseptene i nesten hvilket som helst spillutviklingsmiljø.


Forutsetninger

Denne artikkelen innebærer en god del matematikk og geometri, og i mye mindre grad faktisk koding. Et par forutsetninger for denne artikkelen er:

  • En grunnleggende forståelse av enkel vektor matematikk
  • Evnen til å utføre algebraisk matte

Kollisjonsdeteksjon

Det er ganske mange artikler og opplæringsprogrammer over hele Internett, inkludert her på Tuts +, som dekker kollisjonsdeteksjon. Å vite dette, vil jeg gjerne løpe gjennom emnet veldig fort, siden denne delen ikke er fokus for denne artikkelen.

Aksjeleddbindingskasser

En Aksel Aligned Bounding Box (AABB) er en boks som har sine fire akser i samsvar med koordinatsystemet der den ligger. Dette betyr at det er en boks som ikke kan rotere, og er alltid kvadret ved 90 grader (vanligvis justert med skjermen). Generelt refereres det til som en "begrensende boks" fordi AABBer brukes til å binde andre mer komplekse former.

Et eksempel AABB.

AABB av en kompleks form kan brukes som en enkel test for å se om mer komplekse former innenfor AABBene muligens kan skjære. Men i tilfelle de fleste spill brukes AABB som en grunnleggende form, og binder faktisk ikke noe annet. Strukturen til AABB er viktig. Det er noen forskjellige måter å representere en AABB på, men dette er min favoritt:

struktur AABB Vec2 min; Vec2 max; ;

Dette skjemaet tillater at en AABB representeres av to poeng. Minpunktet representerer nedre grenser på x- og y-aksen, og maks representerer høyere grenser - med andre ord representerer de øverste venstre og nederste høyre hjørner. For å kunne fortelle om to AABB-former skjærer, må du ha en grunnleggende forståelse av Separating Axis Theorem (SAT).

Her er en rask test tatt fra Real-Time Collision Detection av Christer Ericson, som bruker SAT:

bool AABBvsAABB (AABB a, AABB b) // Avslutt uten kryss hvis funnet separert langs en akse hvis (a.max.x < b.min.x or a.min.x > b.max.x) returnere falsk hvis (a.max.y < b.min.y or a.min.y > b.max.y) returnere falsk // Ingen separerende akse funnet, derfor er det minst en overlappende akseavkastning sant

Circles

En sirkel er representert av en radius og et punkt. Her er hva din sirkelstruktur burde se ut som:

struct Circle float radius Vec posisjon;

Testing av om to sirkler krysser eller ikke, er veldig enkle: Ta radiene til de to sirkler og legg dem sammen, og kontroller for å se om denne summen er større enn avstanden mellom de to sirkler.

En viktig optimalisering for å gjøre her er å kvitte seg med behov for å bruke kvadratrotsoperatøren:

float Avstand (Vec2 a, Vec2 b) retur sqrt ((ax-bx) ^ 2 + (ay-by) ^ 2) bool CirclevsCircleUnoptimized (Circle a, Circle 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 

Generelt er multiplikasjon en mye billigere operasjon enn å ta kvadratroten av en verdi.


Impulsoppløsning

Impulsoppløsning er en bestemt type kollisionsoppløsningsstrategi. Kollisjonsoppløsning er en handling av å ta to gjenstander som er funnet å være skjærende og modifisere dem på en slik måte at de ikke lar dem forbli kryssende.

Generelt har et objekt i en fysikkmotor tre hovedgrader av frihet (i to dimensjoner): bevegelse i xy-planet og rotasjon. I denne artikkelen begrenser vi implisitt rotasjon og bruker bare AABBer og kretser, så den eneste frihetsgraden vi virkelig trenger å vurdere er bevegelse langs xy-flyet.

Ved å løse oppdagede kollisjoner legger vi en begrensning på bevegelse slik at objekter ikke kan forbli hverandre. Tanken bak impulsoppløsningen er å bruke en impuls (momentan hastighetsendring) for å skille objekter funnet som kolliderer. For å gjøre dette må masse, posisjon og hastighet for hver gjenstand tas i betraktning på en eller annen måte: Vi vil at store gjenstander kolliderer med mindre for å bevege seg litt under kollisjon, og for å sende de små gjenstandene som flyr bort. Vi vil også ha objekter med uendelig masse for ikke å bevege seg i det hele tatt.

Enkelt eksempel på hvilken impulsoppløsning som kan oppnås.

For å oppnå slike effekter og følge med naturlig intuisjon av hvordan objekter oppfører seg, bruker vi stive legemer og en god del matte. En stiv kropp er bare en form definert av brukeren (det vil si av deg, utvikleren) som er implisitt definert til å være ikke deformerbar. Både AABB og Sirkler i denne artikkelen er ikke deformerbare, og vil alltid være enten en AABB eller Circle. Ingen squashing eller strekking tillatt.

Å arbeide med stive legemer tillater mye matte og avledninger å bli tungt forenklet. Det er derfor stive legemer blir ofte brukt i spillsimuleringer, og hvorfor bruker vi dem i denne artikkelen.

Våre objekter kolliderte - nå hva?

Forutsatt at vi har to former som skjærer, hvordan skiller man egentlig de to? Vi antar at kollisjonsdeteksjonen ga oss to viktige deler av informasjonen:

  • Kollisjon normal
  • Inntrengningsdybde

For å kunne bruke en impuls til begge objektene og flytte dem fra hverandre, må vi vite hvilken retning å presse dem og av hvor mye. Kollisjonen er normal i hvilken retning impulsen skal påføres. Inntrengningsdybden (sammen med noen andre ting) bestemmer hvor stor en impuls vil bli brukt. Dette betyr at den eneste verdien som må løses for, er størrelsen på vår impuls.

La oss nå gå på en lang tur for å finne ut hvordan vi kan løse denne impulsstørrelsen. Vi starter med våre to objekter som har blitt funnet å skjære:

Ligning 1

\ [V ^ AB = V ^ B - V ^ A \] Merk at for å lage en vektor fra posisjon A til posisjon B må du gjøre: endepunkt - startpunkt. \ (V ^ AB \) er den relative hastigheten fra A til B. Denne ligningen burde uttrykkes i forhold til kollisjonen normal \ (n \) - det vil si vi vil vite relativhastigheten fra A til B langs kollisjonens normale retning:

Ligning 2

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

Vi bruker nå prikkproduktet. Prikkproduktet er enkelt; det er summen av komponent-klare produkter:

Ligning 3

\ [V_1 = \ start bmatrix x_1 \\ y_1 \ end bmatrix, V_2 = \ start bmatrix x_2 \\ y_2 \ end bmatrix \\ V_1 \ cdot V_2 = x_1 * x_2 + y_2 * y_2 \ ]

Det neste trinnet er å introdusere det som kalles restitusjonskoeffisient. Restitution er et begrep som betyr elastisitet, eller bounciness. Hvert objekt i din fysikkmotor vil få en restitusjon representert som en desimalverdi. Imidlertid vil bare en desimalverdi bli brukt under impulsberegningen.

For å bestemme hvilken restitusjon som skal brukes (betegnet av \ (e \) for epsilon), bør du alltid bruke den laveste restitusjonen som er involvert i kollisjonen for intuitive resultater:

// Gitt to gjenstander A og B e = min (A.restitution, B.restitution)

Når \ (e \) er anskaffet, kan vi legge den inn i vår ligning for impulsstørrelsen.

Newtons Restitution Law sier følgende:

Ligning 4

\ [V '= e * V \]

Alt dette sier er at hastigheten etter en kollisjon er lik hastigheten før den, multiplisert med noen konstant. Denne konstanten representerer en "sprettfaktor". Å vite dette, blir det ganske enkelt å integrere tilbakebetaling i vår nåværende avledning:

Ligning 5

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

Legg merke til hvordan vi introduserte et negativt tegn her. I Newtons Restitution Law, \ (V '\), den resulterende vektoren etter sprettingen, går faktisk i motsatt retning av V. Så hvordan representerer vi motsatte retninger i vår avledning? Sett inn et negativt tegn.

Så langt så bra. Nå må vi kunne uttrykke disse hastighetene mens de er påvirket av en impuls. Her er en enkel ligning for å modifisere en vektor med noen impulsskalar \ (j \) langs en bestemt retning \ (n \):

Ligning 6

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

Forhåpentligvis er ovennevnte ligning fornuftig, da det er veldig viktig å forstå. Vi har en enhedsvektor \ (n \) som representerer en retning. Vi har en skalar \ (j \) som representerer hvor lenge vår \ (n \) vektor vil være. Vi legger deretter til vår skalerte \ (n \) vektor til \ (V \) for å resultere i \ (V '\). Dette er bare å legge til en vektor på en annen, og vi kan bruke denne lille ligningen til å bruke en impuls fra en vektor til en annen.

Det er litt mer arbeid å gjøre her. Formelt er en impuls definert som en forandring i momentum. Momentum er masse * hastighet. Å vite dette, kan vi representere en impuls som den er formelt definert som:

Ligning 7

\ [Impulse = masse * Velocity \\ Velocity = \ frac Impulse mass \ derfor V '= V + \ frac j * n masse \]

De tre punktene i en liten trekant (\ (\ derfor \)) kan leses som "derfor". Det er vant til å vise at saken på forhånd kan brukes til å konkludere med at det som kommer neste er sant.

Det har blitt gjort gode fremskritt så langt! Imidlertid må vi kunne uttrykke en impuls ved å bruke \ (j \) når det gjelder to forskjellige objekter. Under en kollisjon med objekt A og B, vil A skyves i motsatt retning av B:

Ligning 8

\ Mass = v ^ a + \ frac j * n masse ^ A \\ V '^ B = V ^ B - \ frac j * n masse ^ B \]

Disse to ligningene vil skyve A vekk fra B langs retningsenhetsvektoren \ (n \) ved impulsskalar (størrelsen på \ (n \)) \ (j \).

Alt som nå kreves er å slå sammen ligningene 8 og 5. Vår resulterende ligning vil se slik ut:

Ligning 9

\ N * -E * (V ^ A - V ^ V + \ frac j * n masse ^ A + \ frac j * n masse ^ B) * n = A) \ cdot n \\ \ derfor \\ (V ^ A - V ^ V + \ frac j * n mass ^ A + \ frac j * n masse ^ B) * n + e * (V ^ B - V ^ A) \ cdot n = 0 \]

Hvis du husker, var det opprinnelige målet å isolere vår størrelse. Dette er fordi vi vet hvilken retning å løse kollisjonen i (antatt gitt av kollisjonsdeteksjonen), og har bare igjen å løse størrelsen på denne retningen. Størrelsen som er ukjent i vårt tilfelle er \ (j \); vi må isolere \ (j \) og løse for det.

Ligning 10

\ [(V ^ B - V ^ A) \ cdot n + j * (\ frac j * n mass ^ A + \ frac j * n masse ^ B) * n + e * V ^ B - V ^ A) \ cdot n = 0 \\ \ derfor \\ (1 + e) ​​((V ^ B - V ^ A) \ cdot n) + j * \ frac j * n massen ^ A + \ frac j * n mass ^ B) * n = 0 \\ \ derfor \\ j = \ frac - (1 + e) ​​((V ^ B - V ^ A) \ cdot n) \ frac 1 mass ^ A + \ frac 1 mass ^ B \]

Puh! Det var en god del matte! Det er over alt for nå skjønt. Det er viktig å merke seg at i den endelige versjonen av ligning 10 har vi \ (j \) til venstre (vår størrelse) og alt til høyre er alle kjent. Dette betyr at vi kan skrive noen linjer med kode for å løse for vår impulskalar \ (j \). Og gutten er koden mye mer lesbar enn matematisk notasjon!

void ResolveCollision (Objekt A, Objekt B) // Beregn relativ hastighet Vec2 rv = B.velocity - A.velocity // Beregn relativ hastighet i forhold til normalretningen float velAlongNormal = DotProdukt (rv, normal) // Ikke løs hvis hastigheter skilles hvis (velAlongNormal> 0) returnerer; // Beregn restitusjon flyte e = min (A.restitution, B.restitution) // Beregn impuls scalar float j = - (1 + e) ​​* velAlongNormal j / = 1 / A.mass + 1 / B.mass // Bruk impuls Vec2 impuls = j * normal A. velocity - = 1 / A. mass * impuls B.velocity + = 1 / B.mass * impuls

Det er noen viktige ting å merke seg i koden ovenfor. Det første er sjekken på linje 10, hvis (VelAlongNormal> 0). Denne sjekken er veldig viktig; Det sørger for at du kun løser en kollisjon hvis objektene beveger seg mot hverandre.

To gjenstander kolliderer, men hastigheten vil skille dem neste ramme. Ikke løse denne typen kollisjon.

Hvis objekter beveger seg vekk fra hverandre, vil vi ikke gjøre noe. Dette vil forhindre gjenstander som ikke egentlig skal anses å kollidere fra å løse seg bort fra hverandre. Dette er viktig for å skape en simulering som følger menneskelig intuisjon om hva som skal skje under objektinteraksjon.

Den andre tingen å merke seg er at invers masse beregnes flere ganger uten grunn. Det er best å bare lagre din inverse masse i hvert objekt og forhåndsberegne det en gang:

A.inv_mass = 1 / A.mass
Mange fysikkmotorer lagrer faktisk ikke rå masse. Fysikkmotorer lagrer ofte invers masse og invers masse alene. Det skjer bare så, at de fleste matte som involverer masse, er i form av 1 / masse.

Den siste tingen å merke seg er at vi distribuerer vår impulskalar \ (j \) intelligent over de to objektene. Vi vil at små objekter skal sprette av store objekter med en stor del av \ (j \), og de store objekter har sine hastigheter endret med en veldig liten del av \ (j \).

For å gjøre dette kan du gjøre:

float mass_sum = A.mass + B.mass flyteforhold = A.mass / mass_sum A.velocity - = forhold * impulsforhold = B.mass / mass_sum B.velocity + = forhold * impuls

Det er viktig å innse at koden ovenfor svarer til ResolveCollision () prøvefunksjon fra før. Som sagt tidligere, er inversmasser ganske nyttige i en fysikkmotor.

Sinking Objects

Hvis vi går videre og bruker koden vi har så langt, vil objekter løpe inn i hverandre og sprette av. Dette er flott, selv om hva som skjer hvis en av objektene har uendelig masse? Vel, vi trenger en god måte å representere uendelig masse i vår simulering.

Jeg foreslår at du bruker null som uendelig masse - selv om vi prøver å beregne invers masse av et objekt med null, vil vi ha en divisjon med null. Løsningen til dette er å gjøre følgende når du beregner invers masse:

hvis (A.mass == 0) A.inv_mass = 0 else A.inv_mass = 1 / A.mass

En verdi på null vil resultere i riktige beregninger under impulsoppløsning. Dette er fortsatt greit. Problemet med synkende objekter oppstår når noe begynner å synke inn i et annet objekt på grunn av tyngdekraften. Kanskje noe med lav restitusjon treffer en vegg med uendelig masse og begynner å synke.

Denne synkningen skyldes flytende punktfeil. Under hvert flytende punkt beregnes en liten flytpunktsfeil på grunn av maskinvare. (For mer informasjon, Google [Floating point error IEEE754].) Over tid samles denne feilen i posisjonsfeil, noe som forårsaker at objekter synker inn i hverandre.

For å korrigere denne feilen må det redegjøres for. For å korrigere denne posisjonsfeilen vil jeg vise deg en metode som kalles lineær projeksjon. Lineær projeksjon reduserer penetrasjonen av to gjenstander med en liten prosentandel, og dette utføres etter at impulsen er påført. Posisjonskorrigering er veldig enkel: flytt hvert objekt langs kollisjonen normal \ (n \) med en prosentandel av penetrasjonsdybden:

void PositionalCorrection (Object A, Object B) const float prosent = 0.2 // vanligvis 20% til 80% Vec2 korreksjon = penetrasjonDepth / (A.inv_mass + B.inv_mass)) * prosent * n A.position - = A.inv_mass * korreksjon B.posisjon + = B.inv_mass * korreksjon

Legg merke til at vi skaler på penetrationDepth av den totale massen av systemet. Dette vil gi en posisjonskorrigering proporsjonal med hvor mye masse vi har å gjøre med. Små gjenstander skyver raskere enn tyngre gjenstander.

Det er et lite problem med denne implementeringen: hvis vi alltid løser vår posisjonsfeil, vil objektene jitter frem og tilbake mens de hviler på hverandre. For å unngå dette må det gis noe slakk. Vi utfører bare posisjonskorreksjon hvis penetrasjonen er over noen vilkårlig terskel, referert til som "slop":

void PositionalCorrection (Objekt A, Objekt B) const float prosent = 0.2 // vanligvis 20% til 80% const float slop = 0,01 // vanligvis 0,01 til 0,1 Vec2 korreksjon = maks (penetrasjon - k_slop, 0,0f) / (A. inv_mass + B.inv_mass)) * prosent * n A.posisjon - = A.inv_mass * korreksjon B.posisjon + = B.inv_mass * korreksjon

Dette gjør det mulig for gjenstander å trenge noen gang så lett uten at posisjonskorreksjonen sparker inn.


Simple Manifold Generation

Det siste emnet som skal dekkes i denne artikkelen er enkel manifoldgenerering. EN manifold i matematiske termer er noe i tråd med "en samling poeng som representerer et område i rommet". Men når jeg refererer til begrepet manifold, refererer jeg til en liten gjenstand som inneholder informasjon om en kollisjon mellom to objekter.

Her er et typisk manifoldoppsett:

Struct Manifold Objekt * A; Objekt * B; float penetrasjon; Vec2 normal; ;

Under kollisjonsdeteksjon skal både penetrasjon og kollisjonens normale beregnes. For å finne denne informasjonen må de opprinnelige kollisjonsdeteksjonsalgoritmene fra toppen av denne artikkelen utvides.

Circle vs Circle

La oss starte med den enkleste kollisionsalgoritmen: Circle vs Circle. Denne testen er for det meste trivial. Kan du forestille deg hvilken retning for å løse kollisjonen vil være i? Det er vektoren fra sirkel A til sirkel B. Dette kan oppnås ved å subtrahere Bs posisjon fra A s.

Inntrengningsdybden er relatert til sirkelens radius og avstand fra hverandre. Overlappingen av kretsene kan beregnes ved å subtrahere den summerte radien med avstanden fra hver gjenstand.

Her er en full prøve algoritme for å generere manifolden av en Circle vs Circle kollisjon:

bool CirclevsCircle (Manifold * m) // Oppsett et par pekere til hver objekt Objekt * A = m-> A; Objekt * B = m-> B; // Vekt fra A til B Vec2 n = B-> pos - A-> pos float r = A-> radius + B-> radius r * = r hvis (n.LengthSquared ()> r) return false har kollidert, beregner nå manifold float d = n.Length () // utfør faktisk sqrt // Hvis avstanden mellom sirkler ikke er null hvis (d! = 0) // Avstanden er forskjellen mellom radius og avstand m-> penetrasjon = r - d // Bruk vår d siden vi utførte sqrt på den allerede innenfor Lengde () // Poeng fra A til B, og er en enhet vektor c-> normal = t / d return true // Sirkler er på samme posisjon ellers // Velg tilfeldige (men konsistente) verdier c-> penetrasjon = A-> radius c-> normal = Vec (1, 0) return true

De mest bemerkelsesverdige tingene her er: Vi utfører ikke noen firkantede røtter før det er nødvendig (objekter blir funnet å kollidere), og vi kontrollerer at sirklene ikke er i samme nøyaktige posisjon. Hvis de er i samme posisjon, vil avstanden være null, og vi må unngå divisjon med null når vi beregner t / d.

AABB vs AABB

AABB til AABB test er litt mer kompleks enn Circle vs Circle. Kollisjonen normal vil ikke være vektoren fra A til B, men vil være et ansikt normalt. En AABB er en boks med fire ansikter. Hvert ansikt har en normal. Dette vanlige representerer en enhedsvektor som er vinkelrett på ansiktet.

Undersøk den generelle ligningen av en linje i 2D:

\ [ax + ved + c = 0 \\ normal = \ start bmatrix a \\ b \ end bmatrix \]

I ovennevnte ligning, en og b er den normale vektoren for en linje og vektoren (a, b) antas å bli normalisert (lengden på vektoren er null). Igjen, vår kollisjon normal (retning for å løse kollisjonen) vil være i retning av en av ansiktsnormalene.

Vet du hva c representerer i den generelle ligningen av en linje? c er avstanden fra opprinnelsen. Dette er veldig nyttig for testing for å se om et punkt er på den ene siden av en linje eller en annen, som du vil se i neste artikkel.

Nå er alt som trengs for å finne ut hvilket ansikt som kolliderer på et av objektene med det andre objektet, og vi har det normale. Imidlertid kan noen ganger flere flater av to AABBs krysse, for eksempel når to hjørner krysser hverandre. Dette betyr at vi må finne aksen med minst mulig penetrasjon.

To akser av penetrasjon; Den horisontale x-aksen er minst aksens akse, og denne kollisjonen skal løses langs x-aksen.

Her er en full algoritme for AABB til AABB-manifoldgenerering og kollisjonsdeteksjon:

bool AABBvsAABB (Manifold * m) // Oppsett et par pekere til hver gjenstand Objekt * A = m-> Et objekt * B = m-> B // Vektor fra A til B Vec2 n = B-> pos - A- > pos AABB abox = A-> aabb AABB bbox = B-> aabb // Beregn halvdeler langs x-akse for hvert objekt float a_extent = (abox.max.x - abox.min.x) / 2 float b_extent = .max.x - bbox.min.x) / 2 // Beregn overlapping på x-aksens float x_overlap = a_extent + b_extent - abs (nx) // SAT-test på x-aksen hvis (x_overlap> 0) // Beregn halvdel langs x-aksen for hver gjenstand flyte a_extent = (abox.max.y - abox.min.y) / 2 float b_extent = (bbox.max.y - bbox.min.y) / 2 // Beregn overlapping på y-aksens flyte y_overlap = a_extent + b_extent - abs (ny) // SAT test på y-aksen hvis (y_overlap> 0) // Finn ut hvilken akse som er minst aksens akse hvis (x_overlap> y_overlap) // Punkt mot B som vet at n poeng fra A til B hvis (nx < 0) m->normal = Vec2 (-1, 0) ellers m-> normal = Vec2 (0, 0) m-> penetrasjon = x_overlap return true ellers // Punkt mot B ved at n poeng fra A til B hvis (n.y < 0) m->normal = Vec2 (0, -1) ellers m-> normal = Vec2 (0, 1) m-> penetrasjon = y_overlap return true

Circle vs AABB

Den siste testen jeg vil dekke er Circle vs AABB testen. Tanken her er å beregne nærmeste punkt på AABB til sirkelen; derfra går testen inn i noe som ligner på Circle vs Circle-testen. Når det nærmeste punktet er beregnet og en kollisjon oppdages, er det normale retningen til nærmeste punkt i sirkelens senter. Inntrengningsdybden er forskjellen mellom avstanden til det nærmeste punktet til sirkelen og radiusen til sirkelen.


AABB til sirkelskjæringsdiagram.

Det er en vanskelig spesiell sak; hvis sirkelens senter ligger innenfor AABB, må sirkelens senter klippes til nærmeste kant av AABB, og normal må vendes.

bool AABBvsCircle (Manifold * m) // Oppsett et par pekere til hver gjenstand Objekt * A = m-> Et objekt * B = m-> B // Vektor fra A til B Vec2 n = B-> pos - A- > pos // Nærmeste punkt på A til sentrum av B Vec2 nærmeste = n // Beregn halvdeler langs hver akseflate x_extent = (A-> aabb.max.x - A-> aabb.min.x) / 2 float y_extent = (A-> aabb.max.y - A-> aabb.min.y) / 2 // Klempunkt til kantene på AABB nærmest.x = Klemme (-x_extent, x_extent, nærmeste.x) nærmeste.y = Clamp (-y_extent, y_extent, nearest.y) bool inne = false // Sirkel er inne i AABB, så vi må klemme sirkelens senter // til nærmeste kant hvis (n == nærmeste) inside = true // Finn nærmeste akse hvis (abs (nx)> abs (ny)) // Klem i nærmeste grad hvis (nærmest.x> 0) nærmest.x = x_extent annet nærmeste.x = -x_extent // y-aksen er kortere ellers // Klem i nærmeste grad hvis (nærmest.y> 0) nærmest.y = y_extent annet nærmest.y = -y_extent Vec2 normal = n - nærmest ekte d = normal.LengthSquared () real r = B-> radius // Ea rly ut av radiusen er kortere enn avstanden til nærmeste punkt og // Sirkel ikke inne i AABB hvis (d> r * r &&! inne) returnerer falsk // Unngå sqrt til vi trengte d = sqrt (d) // Kollisjon normal må vendes for å peke ut om sirkelen var // innenfor AABB hvis (innsiden) m-> normal = -n m-> penetrasjon = r - d ellers m-> normal = n m-> penetrasjon = r - d returner sann

Konklusjon

Forhåpentligvis nå har du lært noe eller to om fysikk simulering. Denne opplæringen er nok til å la deg sette opp en enkel, tilpasset fysikkmotor helt fra grunnen. I neste del vil vi dekke alle nødvendige utvidelser som alle fysikkmotorer krever, inkludert:

  • Sortering og sletting av kontaktpar
  • Broadphase
  • lagdeling
  • Integrering
  • Timestepping
  • Halfspace skjæringspunktet
  • Modulær design (materialer, masse og krefter)

Jeg håper du likte denne artikkelen, og jeg gleder meg til å svare på spørsmål i kommentarene.