Een aangepaste 2D-physics-engine maken de basisprincipes en impulsresolutie

Er zijn vele redenen waarom u een aangepaste physics-engine zou willen maken: ten eerste, het aanleren en aanscherpen van uw vaardigheden in wiskunde, natuurkunde en programmeren zijn goede redenen om een ​​dergelijk project te proberen; ten tweede kan een engine voor aangepaste physics elk technisch effect aanpakken dat de maker nodig heeft om te creëren. In dit artikel wil ik een gedegen introductie geven over hoe je een aangepaste physics-engine volledig vanuit het niets kunt maken.

Natuurkunde biedt een prachtig middel om een ​​speler toe te laten zich in een spel onder te dompelen. Het is logisch dat het beheersen van een fysica-engine een krachtige troef is voor elke programmeur die tot zijn beschikking staat. Optimalisaties en specialisaties kunnen op elk moment worden gemaakt dankzij een goed begrip van de interne werking van de physics engine.

Aan het einde van deze zelfstudie zijn de volgende onderwerpen behandeld, in twee dimensies:

  • Eenvoudige botsingsdetectie
  • Eenvoudige generatie van manifolds
  • Impuls resolutie

Hier is een snelle demo:

Notitie: Hoewel deze tutorial met C ++ is geschreven, zou je in bijna elke game-ontwikkelomgeving dezelfde technieken en concepten moeten kunnen gebruiken.


voorwaarden

Dit artikel bevat een behoorlijke hoeveelheid wiskunde en meetkunde, en in veel mindere mate de daadwerkelijke codering. Een paar vereisten voor dit artikel zijn:

  • Een basiskennis van eenvoudige vector wiskunde
  • Het vermogen om algebraïsche wiskunde uit te voeren

Collision Detection

Er zijn een flink aantal artikelen en tutorials over het internet, waaronder hier op Tuts +, die botsingsdetectie dekken. Als ik dit weet, wil ik het onderwerp heel snel doornemen omdat dit gedeelte niet centraal staat in dit artikel.

Axis Aligned Bounding Boxes

Een Axis Aligned Bounding Box (AABB) is een kader waarvan de vier assen zijn uitgelijnd met het coördinatensysteem waarin het zich bevindt. Dit betekent dat het een doos is die niet kan roteren en altijd vierkant wordt uitgezet met een hoek van 90 graden (meestal uitgelijnd met het scherm). In het algemeen wordt dit een "begrenzingsvak" genoemd omdat AABB's worden gebruikt om andere, meer complexe vormen te verbinden.

Een voorbeeld AABB.

De AABB van een complexe vorm kan worden gebruikt als een eenvoudige test om te zien of meer complexe vormen binnen de AABB's elkaar mogelijk kunnen kruisen. In het geval van de meeste spellen wordt de AABB echter als een fundamentele vorm gebruikt en eigenlijk niets anders gebonden. De structuur van je AABB is belangrijk. Er zijn een paar verschillende manieren om een ​​AABB te vertegenwoordigen, maar dit is mijn favoriet:

struct AABB Vec2 min; Vec2 max; ;

Met dit formulier kan een AABB worden vertegenwoordigd door twee punten. Het min punt vertegenwoordigt de ondergrenzen van de x- en y-as, en max vertegenwoordigt de hogere grenzen - met andere woorden, ze vertegenwoordigen de linkerbovenhoek en rechteronderhoek. Om te weten of twee AABB-vormen elkaar kruisen, moet je een basiskennis hebben van de theorie van de scheidende as (SAT).

Hier is een snelle test van Real-Time Collision Detection van Christer Ericson, die gebruik maakt van de SAT:

bool AABBvsAABB (AABB a, AABB b) // Sluit af zonder kruising indien gevonden gescheiden langs een as als (a.max.x < b.min.x or a.min.x > b.max.x) return false als (a.max.y < b.min.y or a.min.y > b.max.y) return false // Geen gevonden scheidingslijn, daarom is er ten minste één overlappende as die true retourneert

cirkels

Een cirkel wordt weergegeven door een straal en een punt. Hier is wat je cirkelstructuur eruit zou moeten zien:

struct Circle zwevende straal Vec positie;

Testen of twee cirkels elkaar kruisen is heel eenvoudig: neem de radii van de twee cirkels en voeg ze samen toe, en controleer vervolgens of deze som groter is dan de afstand tussen de twee cirkels.

Een belangrijke optimalisatie om hier te maken is om geen gebruik meer te maken van de vierkantsworteloperator:

zweven Afstand (Vec2 a, Vec2 b) return sqrt ((ax - bx) ^ 2 + (ay - by) ^ 2) bool CirclevsCircleUnoptimized (Cirkel a, Cirkel b) zwevend r = a.radius + b.radius terugkeer 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 

Over het algemeen is vermenigvuldiging een veel goedkopere bewerking dan het nemen van de vierkantswortel van een waarde.


Impuls resolutie

Impulsresolutie is een specifiek type botsresolutiestrategie. Botsingsresolutie is het nemen van twee objecten waarvan wordt vastgesteld dat ze elkaar snijden en op zodanige manier wijzigen dat ze elkaar niet overlappen.

Over het algemeen heeft een object binnen een fysica-engine drie hoofdvrijheidsgraden (in twee dimensies): beweging in het xy-vlak en rotatie. In dit artikel beperken we impliciet de rotatie en gebruiken we alleen AABB's en cirkels, dus de enige mate van vrijheid die we echt moeten overwegen, is beweging langs het xy-vlak.

Door het oplossen van gedetecteerde botsingen plaatsen we een beperking op beweging zodat objecten elkaar niet kunnen blijven snijden. Het idee achter impulsresolutie is om een ​​impuls (onmiddellijke verandering in snelheid) te gebruiken om objecten te vinden die tegen elkaar botsen. Om dit te doen moet op de een of andere manier rekening worden gehouden met de massa, positie en snelheid van elk object: we willen dat grote objecten die met kleinere botsen tijdens de botsing een beetje bewegen en de kleine voorwerpen wegvliegen. We willen ook dat objecten met oneindige massa helemaal niet bewegen.

Eenvoudig voorbeeld van wat impulsresolutie kan bereiken.

Om dergelijke effecten te bereiken en mee te gaan met de natuurlijke intuïtie van hoe objecten zich gedragen, gebruiken we rigide lichamen en een behoorlijk beetje wiskunde. Een onbuigzaam lichaam is slechts een vorm die wordt gedefinieerd door de gebruiker (dat wil zeggen door u, de ontwikkelaar) die impliciet is gedefinieerd als niet-vervormbaar. Beide AABB's en cirkels in dit artikel zijn niet-vervormbaar en zullen altijd een AABB of een cirkel zijn. Niet pletten of rekken toegestaan.

Werken met rigide lichamen zorgt ervoor dat veel wiskunde en afleidingen sterk vereenvoudigd kunnen worden. Daarom worden rigide lichamen vaak gebruikt in spelsimulaties en daarom gebruiken we ze in dit artikel.

Onze objecten botsten - wat nu?

Ervan uitgaande dat we twee vormen hebben die elkaar kruisen, hoe scheid je die dan eigenlijk van elkaar? Laten we aannemen dat onze botsingsdetectie ons twee belangrijke stukjes informatie gaf:

  • Botsing normaal
  • Penetratie diepte

Om een ​​impuls toe te passen op beide objecten en ze uit elkaar te halen, moeten we weten in welke richting ze moeten worden geduwd en door hoeveel. De botsingsnormaal is de richting waarin de impuls zal worden toegepast. De penetratiediepte (samen met enkele andere dingen) bepalen hoe groot een impuls zal zijn. Dit betekent dat de enige waarde die moet worden opgelost, de omvang van onze impuls is.

Laten we nu een lange tocht maken om te ontdekken hoe we deze impulsmagnitude kunnen oplossen. We beginnen met onze twee objecten waarvan is vastgesteld dat ze elkaar kruisen:

Vergelijking 1

\ [V ^ AB = V ^ B - V ^ A \] Let op dat je, om een ​​vector te maken van positie A naar positie B, moet doen: eindpunt - startpunt. \ (V ^ AB \) is de relatieve snelheid van A tot B. Deze vergelijking moet worden uitgedrukt in termen van de botsingsnormaal \ (n \) - dat wil zeggen, we zouden graag de relatieve snelheid van A willen weten B langs de richting van de botsing normaal:

Vergelijking 2

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

We maken nu gebruik van het puntproduct. Het puntproduct is eenvoudig; het is de som van componentgewijze producten:

Vergelijking 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 \ ]

De volgende stap is het introduceren van wat de ' restitutiecoëfficiënt. Restitutie is een term die elasticiteit of bounciness betekent. Elk object in uw physics engine krijgt een restitutie weergegeven als een decimale waarde. Er wordt echter slechts één decimale waarde gebruikt tijdens de impulsberekening.

Om te bepalen welke restitutie u moet gebruiken (aangegeven met \ (e \) voor epsilon), moet u altijd de laagste restitutie gebruiken die bij de botsing is betrokken voor intuïtieve resultaten:

// Gegeven twee objecten A en B e = min (A.restitutie, B.restitutie)

Zodra \ (e \) is verworven, kunnen we het in onze vergelijking plaatsen om de impulsgrootheid op te lossen.

Newton's Restitutiewet vermeldt het volgende:

Vergelijking 4

\ [V '= e * V \]

Dit alles zegt dat de snelheid na een botsing gelijk is aan de snelheid ervoor, vermenigvuldigd met een constante. Deze constante vertegenwoordigt een "bounce-factor". Dit wetende, wordt het redelijk eenvoudig om restitutie te integreren in onze huidige afleiding:

Vergelijking 5

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

Merk op hoe we hier een negatief teken hebben geïntroduceerd. In de wet van teruggave van Newton, \ (V '\), gaat de resulterende vector na de bounce eigenlijk in de tegenovergestelde richting van V. Dus hoe vertegenwoordigen we tegengestelde richtingen in onze afleiding? Introduceer een negatief teken.

Tot zover goed. Nu moeten we in staat zijn om deze snelheden uit te drukken terwijl we onder invloed zijn van een impuls. Hier is een eenvoudige vergelijking voor het wijzigen van een vector met behulp van een impulsschaal \ (j \) langs een specifieke richting \ (n \):

Vergelijking 6

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

Hopelijk is de bovenstaande vergelijking logisch, omdat het heel belangrijk is om te begrijpen. We hebben een eenheidsvector \ (n \) die een richting aangeeft. We hebben een scalar \ (j \) die aangeeft hoe lang onze \ (n \) vector zal zijn. Vervolgens voegen we onze geschaalde \ (n \) vector toe aan \ (V \) om te resulteren in \ (V '\). Dit is gewoon de ene vector aan de andere toevoegen en we kunnen deze kleine vergelijking gebruiken om een ​​impuls van de ene vector naar de andere toe te passen.

Er is hier iets meer werk aan de winkel. Formeel wordt een impuls gedefinieerd als een verandering in momentum. Momentum is massa * snelheid. Als we dit weten, kunnen we een impuls vertegenwoordigen, zoals het formeel zo wordt gedefinieerd:

Vergelijking 7

\ [Impulse = massa * Velocity \\ Velocity = \ frac Impulse massa \ daarom V '= V + \ frac j * n massa \]

De drie punten in een kleine driehoek (\ (\ daarom \)) kunnen als "dus" worden gelezen. Het wordt gebruikt om aan te tonen dat het ding van tevoren kan worden gebruikt om te concluderen dat wat daarna komt waar is.

Tot nu toe is goede vooruitgang geboekt! We moeten echter een impuls kunnen uitdrukken met behulp van \ (j \) in termen van twee verschillende objecten. Tijdens een botsing met object A en B wordt A in de tegenovergestelde richting van B geduwd:

Vergelijking 8

\ [V '^ A = V ^ A + \ frac j * n massa ^ A \\ V' ^ B = V ^ B - \ frac j * n massa ^ B \]

Deze twee vergelijkingen duwen A weg van B langs de richtingeenheidvector \ (n \) door impulsscalair (grootte van \ (n \)) \ (j \).

Het enige dat nu nodig is, is Vergelijkingen 8 en 5 samen te voegen. Onze resulterende vergelijking zal er ongeveer zo uitzien:

Vergelijking 9

\ [(V ^ A - V ^ V + \ frac j * n massa ^ A + \ frac j * n massa ^ B) * n = -e * (V ^ B - V ^ A) \ cdot n \\ \ daarom \\ (V ^ A - V ^ V + \ frac j * n massa ^ A + \ frac j * n massa ^ B) * n + e * (V ^ B - V ^ A) \ cdot n = 0 \]

Als je je herinnert, was het oorspronkelijke doel om onze omvang te isoleren. Dit komt omdat we weten in welke richting de botsing moet worden opgelost (aangenomen door de botsingsdetectie) en alleen nog zijn overgelaten om de omvang van deze richting op te lossen. De magnitude die in ons geval onbekend is, is \ (j \); we moeten \ (j \) isoleren en oplossen.

Vergelijking 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 \\ \ daarom \\ (1 + e) ​​((V ^ B - V ^ A) \ cdot n) + j * (\ frac j * n massa ^ A + \ frac j * n massa ^ B) * n = 0 \\ \ daarom \\ j = \ frac - (1 + e) ​​((V ^ B - V ^ A) \ cdot n) \ frac 1 massa ^ A + \ frac 1 massa ^ B \]

Oef! Dat was een aardig woordje wiskunde! Maar het is nu allemaal voorbij. Het is belangrijk om op te merken dat in de definitieve versie van Vergelijking 10 we \ (j \) links (onze grootte) hebben en dat alles aan de rechterkant allemaal bekend is. Dit betekent dat we een paar coderegels kunnen schrijven om op te lossen voor onze impulsschaal \ (j \). En jongen is de code veel leesbaarder dan wiskundige notatie!

void ResolveCollision (Object A, Object B) // Bereken relatieve snelheid Vec2 rv = B.velocity - A.velocity // Bereken relatieve snelheid in termen van de normale richting float velAlongNormal = DotProduct (rv, normaal) // Los niet op als de snelheden scheiden als (velAlongNormal> 0) terugkeren; // Bereken restitutie float e = min (A.restitutie, B.restitutie) // Bereken impulse scalaire float j = - (1 + e) ​​* velAlongNormal j / = 1 / A.mass + 1 / B.mass // Toepassen impuls Vec2 impulse = j * normaal A.velocity - = 1 / A.mass * impuls B.velocity + = 1 / B.mass * impuls

Er zijn een paar belangrijke dingen om op te merken in het bovenstaande codevoorbeeld. Het eerste is de controle op lijn 10, if (VelAlongNormal> 0). Deze controle is erg belangrijk; het zorgt ervoor dat je alleen een botsing oplost als de objecten naar elkaar toe bewegen.

Twee objecten botsen, maar de snelheid scheidt ze van het volgende beeld. Dit type botsing niet oplossen.

Als objecten zich van elkaar verwijderen, willen we niets doen. Hiermee wordt voorkomen dat objecten die eigenlijk niet als botsende objecten worden beschouwd, van elkaar worden gescheiden. Dit is belangrijk voor het maken van een simulatie die de menselijke intuïtie volgt op wat er moet gebeuren tijdens objectinteractie.

Het tweede ding om op te merken is dat de inverse massa zonder reden meerdere keren wordt berekend. Het is het beste om gewoon uw inverse massa in elk object op te slaan en het een keer voor te berekenen:

A.inv_mass = 1 / A.mass
Veel natuurkundemotoren slaan geen ruwe massa op. Natuurkundige motoren slaan vaak alleen de inverse massa en de inverse massa op. Het gebeurt gewoon zo dat de meeste wiskunde met betrekking tot massa de vorm heeft van 1 / mass.

Het laatste wat op te merken is, is dat we onze impulse scalaire \ (j \) intelligent over de twee objecten verdelen. We willen dat kleine objecten van grote objecten afketsen met een groot deel van \ (j \), en dat de grote objecten hun snelheden laten wijzigen door een heel klein deel van \ (j \).

Om dit te doen zou je kunnen doen:

float mass_sum = A.mass + B.mass float ratio = A.mass / mass_sum A.velocity - = ratio * impulsverhouding = B.mass / mass_sum B. swift + = ratio * impuls

Het is belangrijk om te beseffen dat de bovenstaande code gelijk is aan de ResolveCollision () voorbeeldfunctie van voordien. Zoals eerder vermeld, zijn inverse massa's erg handig in een physics-engine.

Zinkende objecten

Als we doorgaan en de code gebruiken die we tot nu toe hebben, zullen objecten elkaar tegenkomen en weerkaatsen. Dit is geweldig, maar wat gebeurt er als een van de objecten een oneindige massa heeft? Welnu, we hebben een goede manier nodig om oneindige massa te vertegenwoordigen in onze simulatie.

Ik stel voor om nul als oneindige massa te gebruiken - hoewel als we de inverse massa van een object met nul proberen te berekenen, we een deling door nul zullen hebben. De oplossing hiervoor is om het volgende te doen bij het berekenen van de inverse massa:

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

Een waarde van nul zal resulteren in de juiste berekeningen tijdens de impulsresolutie. Dit is nog steeds goed. Het probleem van zinkende voorwerpen ontstaat wanneer iets door de zwaartekracht in een ander object zinkt. Misschien raakt iets met lage restitutie een muur met oneindige massa en begint het te zinken.

Dit zinken wordt veroorzaakt door drijvende-komma-fouten. Tijdens elke berekening met drijvende komma's wordt een fout met een drijvende komma geïntroduceerd als gevolg van hardware. (Voor meer informatie, Google [Floating point error IEEE754].) Na verloop van tijd accumuleert deze fout in de positionele fout, waardoor objecten in elkaar zinken.

Om deze fout te corrigeren, moet deze worden verantwoord. Om deze positionele fout te corrigeren, laat ik je een methode zien met de naam lineaire projectie. Lineaire projectie vermindert de penetratie van twee objecten met een klein percentage, en dit wordt uitgevoerd nadat de impuls is toegepast. Positionele correctie is heel eenvoudig: verplaats elk object langs de botsing normaal \ (n \) met een percentage van de penetratiediepte:

void PositionalCorrection (Object A, Object B) const floatpercentage = 0.2 // meestal 20% tot 80% Vec2-correctie = penetratiediepte / (A.inv_mass + B.inv_mass)) * percent * n A.position - = A.inv_mass * correctie B.position + = B.inv_mass * correctie

Merk op dat we het penetratie diepte door de totale massa van het systeem. Dit geeft een positionele correctie die evenredig is aan de massa waarmee we te maken hebben. Kleine voorwerpen duwen sneller weg dan zwaardere voorwerpen.

Er is een klein probleem met deze implementatie: als we onze positionele fout altijd oplossen, zullen objecten heen en weer bewegen terwijl ze op elkaar rusten. Om dit te voorkomen moet enige speling worden gegeven. We voeren alleen positionele correctie uit als de penetratie boven een willekeurige drempel ligt, aangeduid als "slop":

void PositionalCorrection (Object A, Object B) const floatpercentage = 0.2 // meestal 20% tot 80% const float slop = 0.01 // meestal 0.01 tot 0.1 Vec2 correctie = max (penetratie - k_slop, 0.0f) / (A. inv_mass + B.inv_mass)) * percent * n A.position - = A.inv_mass * correctie B.position + = B.inv_mass * correctie

Hierdoor kunnen voorwerpen enigszins doordringen zonder dat de positiecorrectie begint.


Simple Manifold Generation

Het laatste onderwerp dat in dit artikel aan de orde komt, is de eenvoudige generatie van het spruitstuk. EEN verdeelstuk in wiskundige termen is het iets in de trant van "een verzameling punten die een gebied in de ruimte vertegenwoordigt". Nochtans, wanneer ik naar de term veelvoud verwijs, verwijs ik naar een klein voorwerp dat informatie over een botsing tussen twee voorwerpen bevat.

Hier is een typische installatie van het spruitstuk:

struct Manifold Object * A; Object * B; vlotterpenetratie; Vec2 normaal; ;

Tijdens botsingsdetectie moeten zowel de penetratie als de botsingsnorm worden berekend. Om deze informatie te vinden, moeten de oorspronkelijke botsingsdetectiealgoritmen van de bovenkant van dit artikel worden uitgebreid.

Cirkel versus cirkel

Laten we beginnen met het eenvoudigste botsingsalgoritme: Cirkel versus Cirkel. Deze test is meestal triviaal. Kun je je voorstellen wat de richting is om de botsing op te lossen? Het is de vector van cirkel A naar cirkel B. Dit kan worden verkregen door B's positie af te trekken van A's.

De penetratiediepte is gerelateerd aan de stralen van de Cirkels en de afstand tot elkaar. De overlapping van de cirkels kan worden berekend door de gesommeerde radii af te trekken op de afstand van elk object.

Hier is een volledig voorbeeldalgoritme voor het genereren van de variëteit van een cirkel- versus cirkelbotsing:

bool CirclevsCircle (Manifold * m) // Stel een paar pointers in op elk object Object * A = m-> A; Object * B = m-> B; // Vector van A naar B Vec2 n = B-> pos - A-> pos float r = A-> straal + B-> straal r * = r if (n.LengthSquared ()> r) return false // Circles zijn gecounterd, berekenen nu veelvuldig zweven d = n.Lengte () // voer werkelijke sqrt uit // Als afstand tussen cirkels niet nul is als (d! = 0) // afstand is verschil tussen straal en afstand m-> penetratie = r - d // Gebruik onze d omdat we al sqrt op het hebben uitgevoerd binnen Lengte () // Punten van A naar B, en is een eenheidsvector c-> normaal = t / d terug waar Cirkels zijn op dezelfde positie else // Kies willekeurige (maar consistente) waarden c-> penetratie = A-> straal c-> normaal = vec (1, 0) retourneer waar

De meest opvallende dingen hier zijn: we voeren geen vierkante wortels uit totdat dit nodig is (objecten blijken bot te zijn) en we controleren of de cirkels niet op dezelfde exacte positie staan. Als ze zich op dezelfde positie bevinden, zou onze afstand nul zijn en moeten we delen door nul vermijden wanneer we berekenen t / d.

AABB versus AABB

De AABB tot AABB-test is iets complexer dan Circle vs Circle. De botsingsnormaal is niet de vector van A naar B, maar is normaal. Een AABB is een doos met vier gezichten. Elk gezicht heeft een normaal gezicht. Deze normaal vertegenwoordigt een eenheidsvector die loodrecht op het gezicht staat.

Bestudeer de algemene vergelijking van een regel in 2D:

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

In de bovenstaande vergelijking, een en b zijn de normale vector voor een lijn en de vector (a, b) wordt verondersteld genormaliseerd te zijn (lengte van vector is nul). Nogmaals, onze botsing normaal (richting om de botsing op te lossen) zal in de richting van één van de gezichtnormen zijn.

Weet je wat c staat voor in de algemene vergelijking van een regel? c is de afstand vanaf de oorsprong. Dit is erg handig om te testen of een punt zich aan de ene of andere kant van een lijn bevindt, zoals u in het volgende artikel zult zien.

Nu is het enige dat nodig is om erachter te komen welk gezicht botst op een van de objecten met het andere voorwerp, en we hebben onze normaal. Soms kunnen echter meerdere vlakken van twee AABB's elkaar snijden, bijvoorbeeld wanneer twee hoeken elkaar kruisen. Dit betekent dat we het moeten vinden as van de minste penetratie.

Twee assen van penetratie; de horizontale x-as is de as met de laagste penetratie en deze botsing moet langs de x-as worden opgelost.

Hier is een volledig algoritme voor AABB naar AABB spruitstukgeneratie en botsingsdetectie:

bool AABBvsAABB (Manifold * m) // Stel een paar pointers in op elk object Object * A = m-> Een object * B = m-> B // Vector van A tot B Vec2 n = B-> pos - A- > pos AABB abox = A-> aabb AABB bbox = B-> aabb // Bereken halve extents langs x-as voor elk object float a_extent = (abox.max.x - abox.min.x) / 2 float b_extent = (bbox .max.x - bbox.min.x) / 2 // Bereken overlapping op x-as zwevend x_overlap = a_extent + b_extent - abs (nx) // SAT-test op x-as if (x_overlap> 0) // Bereken de helft van extents langs x-as voor elk object zwevend a_extent = (abox.max.y - abox.min.y) / 2 zwevend b_extent = (bbox.max.y - bbox.min.y) / 2 // Bereken overlapping op y-as zwevend y_overlap = a_extent + b_extent - abs (ny) // SAT-test op y-as if (y_overlap> 0) // Zoek uit welke as as van de minste penetratie is als (x_overlap> y_overlap) // wijs naar B wetende dat n punten van A tot B als (nx < 0) m->normaal = vec2 (-1, 0) anders m-> normaal = vec2 (0, 0) m-> penetratie = x_overlap return true else // wijs richting B wetende dat n punten van A naar B als (n.y < 0) m->normaal = vec2 (0, -1) anders m-> normaal = vec2 (0, 1) m-> penetratie = y_overlap return true

Cirkel versus AABB

De laatste test die ik zal behandelen, is de Circle vs AABB-test. Het idee hier is om het dichtstbijzijnde punt op de AABB naar de Circle te berekenen; vanaf daar gaat de test over in iets soortgelijks als de Circle vs Circle-test. Zodra het dichtstbijzijnde punt is berekend en een botsing is gedetecteerd, is de normaalrichting de richting van het punt dat het dichtst bij het midden van de cirkel ligt. De penetratiediepte is het verschil tussen de afstand van het dichtstbijzijnde punt tot de cirkel en de straal van de cirkel.


AABB naar cirkel snijpuntdiagram.

Er is één lastig speciaal geval; als het midden van de cirkel zich binnen de AABB bevindt, dan moet het midden van de cirkel worden afgekapt tot de dichtstbijzijnde rand van de AABB, en de normaal moet worden omgedraaid.

bool AABBvsCircle (Manifold * m) // Stel een paar pointers in op elk object Object * A = m-> Een object * B = m-> B // Vector van A tot B Vec2 n = B-> pos - A- > pos // Dichtstbijzijnde punt op A tot midden van B Vec2 dicht = n // Bereken halve lengte langs elke zwevend zwevende as x_extent = (A-> aabb.max.x - A-> aabb.min.x) / 2 zwevend y_extent = (A-> aabb.max.y - A-> aabb.min.y) / 2 // Klempunt op randen van de AABB closest.x = Klem (-x_extent, x_extent, closest.x) closest.y = Clamp (-y_extent, y_extent, closest.y) bool inside = false // Circle bevindt zich binnen de AABB, dus we moeten het midden van de cirkel // vastzetten aan de dichtstbijzijnde rand als (n == closest) inside = true // Zoek dichtstbijzijnde as als (abs (nx)> abs (ny)) // Klem in de nauwste mate als (closest.x> 0) closest.x = x_extent else nearest.x = -x_extent // y-as is korter anders // Klem in de nauwste mate als (het dichtst bij> 0) het dichtst bij is.y = y_extent anders closest.y = -y_extent Vec2 normaal = n - het dichtst echt d = normal.LengthSquared () real r = B-> radius // Ea rly buiten de straal is korter dan de afstand tot het dichtstbijzijnde punt en // Cirkel niet binnen de AABB als (d> r * r && binnen) false terugkomt // Vermeden sqrt totdat we nodig hadden d = sqrt (d) // Aanvaringsnormaal moet naar buiten worden gekeerd als cirkel // in de AABB was als (binnen) m-> normaal = -n m-> penetratie = r - d anders m-> normaal = n m-> penetratie = r - d return true

Conclusie

Hopelijk heb je nu een ding geleerd over simulatie van natuurkunde. Deze zelfstudie is voldoende om u een eenvoudige engine voor aangepaste physics te laten maken die helemaal vanaf nul is gemaakt. In het volgende deel zullen we alle noodzakelijke uitbreidingen behandelen die alle fysica-engines vereisen, waaronder:

  • Contactpair sorteren en ruimen
  • Broadphase
  • gelaagdheid
  • integratie
  • Timestepping
  • Kruising halfruimte
  • Modulair ontwerp (materialen, massa en krachten)

Ik hoop dat je dit artikel leuk vond en ik kijk uit naar het beantwoorden van vragen in de comments.