Tot dusverre hebben we de impulsresolutie, de kernarchitectuur en wrijving behandeld. In deze laatste tutorial in deze serie bespreken we een heel interessant onderwerp: oriëntatie.
In dit artikel bespreken we de volgende onderwerpen:
Ik raad ten zeerste aan de vorige drie artikelen in de serie te lezen voordat ik deze probeerde aan te pakken. Veel van de belangrijkste informatie in de vorige artikelen zijn vereisten voor de rest van dit artikel.
Ik heb een kleine voorbeeldengine gemaakt in C ++, en ik raad aan dat je tijdens het lezen van dit artikel door de broncode bladert en naar de broncode verwijst, omdat veel praktische implementatiedetails niet in het artikel zelf konden passen.
Deze GitHub-repo bevat de voorbeeldengine zelf, samen met een Visual Studio 2010-project. Met GitHub kun je de bron bekijken zonder de bron zelf te hoeven downloaden.
gerelateerde berichtenDe wiskunde met rotaties in 2D is vrij eenvoudig, hoewel een beheersing van het onderwerp vereist zal zijn om iets van waarde te creëren in een fysica-engine. De tweede wet van Newton bepaalt:
\ [Vergelijking \: 1: \\
F = ma \]
Er is een vergelijkbare vergelijking die specifiek betrekking heeft op hoekkracht en hoekversnelling. Voordat deze vergelijkingen kunnen worden weergegeven, is echter een korte beschrijving van het crossproduct in 2D vereist.
Het crossproduct in 3D is een bekende bewerking. Het crossproduct in 2D kan echter behoorlijk verwarrend zijn, omdat er niet echt een solide geometrische interpretatie is.
Het 2D-crossproduct retourneert, in tegenstelling tot de 3D-versie, geen vector maar een scalaire waarde. Deze scalaire waarde vertegenwoordigt feitelijk de grootte van de orthogonale vector langs de z-as, als het crossproduct daadwerkelijk in 3D zou worden uitgevoerd. In zekere zin is het 2D-kruisproduct slechts een vereenvoudigde versie van het 3D-kruisproduct, omdat het een uitbreiding is van 3D-vector wiskunde.
Als dit verwarrend is, maakt u zich geen zorgen: een grondig begrip van het 2D-crossproduct is niet alles dat nodig is. Weet precies hoe de bewerking moet worden uitgevoerd en weet dat de volgorde van bewerkingen belangrijk is: \ (a \ times b \) is niet hetzelfde als \ (b \ maal a \). Dit artikel zal intens gebruik maken van het crossproduct om de hoeksnelheid om te zetten in lineaire snelheid.
Weten hoe om het crossproduct in 2D uit te voeren is echter erg belangrijk. Twee vectoren kunnen worden gekruist, een scalair kan worden gekruist met een vector en een vector kan worden gekruist met een scalair. Dit zijn de operaties:
// Twee gekruiste vectoren geven een scalaire dobber terug CrossProduct (const Vec2 & a, const Vec2 & b) return a.x * b.y - a.y * b.x; // Meer exotische (maar noodzakelijke) vormen van het kruisproduct // met een vector a en scalar s, beide keren een vector terug Vec2 CrossProduct (const Vec2 & a, float s) return Vec2 (s * ay, -s * axe ); Vec2 CrossProduct (float s, const Vec2 & a) return Vec2 (-s * a.y, s * a.x);
Zoals we allemaal uit de vorige artikelen zouden moeten weten, vertegenwoordigt deze vergelijking een relatie tussen de kracht die op een lichaam werkt met de massa en versnelling van dat lichaam. Er is een analoog voor rotatie:
\ [Vergelijking \: 2: \\
T = r \: \ times \: \ omega \]
\ (T \) staat voor torque. Koppel is rotatiekracht.
\ (r \) is een vector van het zwaartepunt (COM) naar een bepaald punt op een object. \ (r \) kan worden beschouwd als verwijzend naar een "radius" van COM tot een punt. Elk uniek punt op een object vereist een andere \ (r \) -waarde die in vergelijking 2 moet worden weergegeven.
\ (\ omega \) wordt "omega" genoemd en verwijst naar rotatiesnelheid. Deze relatie zal worden gebruikt om de hoeksnelheid van een star lichaam te integreren.
Het is belangrijk om te begrijpen dat lineaire snelheid de snelheid is van de COM van een rigide lichaam. In het vorige artikel hadden alle objecten geen rotatiecomponenten, dus de lineaire snelheid van de COM was dezelfde snelheid voor alle punten op een lichaam. Wanneer oriëntatie wordt geïntroduceerd, roteren punten verder weg van de COM sneller dan die nabij de COM. Dit betekent dat we een nieuwe vergelijking nodig hebben om de snelheid van een punt op een lichaam te vinden, omdat lichamen nu tegelijkertijd kunnen draaien en vertalen.
Gebruik de volgende vergelijking om de relatie tussen een punt op een lichaam en de snelheid van dat punt te begrijpen:
\ [Vergelijking \: 3: \\
\ omega = r \: \ maal v \]
\ (v \) staat voor lineaire snelheid. Verplaats de \ (r \) radius met \ (v \) om lineaire snelheid om te zetten in hoeksnelheid.
Evenzo kunnen we vergelijking 3 opnieuw rangschikken om een andere versie te vormen:
\ [Vergelijking \: 4: \\
v = \ omega \: \ times r \]
De vergelijkingen van de laatste sectie zijn alleen erg krachtig als stijve lichamen een uniforme dichtheid hebben. Niet-uniforme dichtheid maakt de wiskunde die betrokken is bij het berekenen van alles wat nodig is de rotatie en het gedrag van een rigide lichaam veel te gecompliceerd. Verder, als het punt dat een rigide lichaam vertegenwoordigt niet bij de COM is, dan zullen de berekeningen met betrekking tot \ (r \) helemaal wonkig zijn.
In twee dimensies draait een object rond de imaginaire z-as. Deze rotatie kan behoorlijk moeilijk zijn, afhankelijk van hoeveel massa een object heeft en hoe ver weg van de COM de massa van het object is. Een cirkel met massa gelijk aan een lange dunne staaf zal gemakkelijker te draaien zijn dan de staaf. Deze "moeilijkheidsgraad om te roteren" factor kan worden gezien als het traagheidsmoment van een object.
In zekere zin is inertie de rotatiemassa van een voorwerp. Hoe meer inertie iets heeft, hoe moeilijker het is om het te laten draaien.
Dit wetende, zou men de traagheid van een object in het lichaam kunnen opslaan als hetzelfde formaat als massa. Het is verstandig om ook de inverse van deze traagheidswaarde op te slaan, waarbij u ervoor zorgt dat er geen deling door nul wordt uitgevoerd. Zie de vorige artikelen in deze serie voor meer informatie over massa en inverse massa.
Voor elk star lichaam zijn nog enkele velden nodig om rotatie-informatie op te slaan. Hier is een snel voorbeeld van een structuur om wat extra gegevens te bewaren:
struct RigidBody Shape * shape // Lineaire componenten Vec2-positie Vec2-snelheidsvlotterversnelling // Hoekige componenten zwevende oriëntatie // radialen zweven angulairVermogen zweeftorsie;
Het integreren van de hoeksnelheid en oriëntatie van een lichaam lijkt erg op de integratie van snelheid en versnelling. Hier is een snel codevoorbeeld om te laten zien hoe het werkt (opmerking: details over integratie werden behandeld in een vorig artikel):
const Vec2 zwaartekracht (0, -10.0f) snelheid + = kracht * (1,0 f / massa + zwaartekracht) * dt hoeksnelheid + = koppel * (1,0 f / moment van intensiteit) * dt positie + = snelheid * dt orient + = angularVelocity * dt
Met de kleine hoeveelheid informatie die tot nu toe is gepresenteerd, zou je in staat moeten zijn om verschillende dingen op het scherm zonder problemen te draaien. Met slechts een paar regels code kan iets behoorlijk indrukwekends worden geconstrueerd, bijvoorbeeld door een vorm in de lucht te gooien terwijl deze rond de COM roteert terwijl de zwaartekracht hem naar beneden trekt om een gebogen rijpad te vormen.
Mat22
Oriëntatie moet worden opgeslagen als een enkele radiale waarde, zoals hierboven te zien is, hoewel vaak het gebruik van een kleine rotatiematrix een veel betere keuze kan zijn voor bepaalde vormen.
Een goed voorbeeld is de Oriented Bounding Box (OBB). De OBB bestaat uit een breedte en hoogte, beide kunnen worden weergegeven door vectoren. Deze twee mate-vectoren kunnen dan worden geroteerd door een twee-bij-twee rotatie-matrix om de assen van een OBB weer te geven.
Ik stel voor de oprichting van een Mat22
matrixklasse die moet worden toegevoegd aan de wiskundebibliotheek die u gebruikt. Ik gebruik zelf een kleine aangepaste wiskundebibliotheek die is ingepakt in de open source-demo. Hier is een voorbeeld van hoe een dergelijk object er kan uitzien:
struct Mat22 union struct float m00, m01 float m10, m11; ; struct Vec2 xCol; Vec2 yCol; ; ; ;
Enkele bruikbare bewerkingen zijn: constructie vanuit hoek, constructie van kolomvectoren, transponeren, vermenigvuldigen met Vec2
, vermenigvuldig met een andere Mat22
, absolute waarde.
De laatste nuttige functie is om de X
of Y
kolom van een vector. De kolombewerking zou er ongeveer zo uitzien:
Mat22 m (PI / 2,0f); Vec2 r = m.ColX (); // haal de x-as kolom op
Deze techniek is handig voor het ophalen van een eenheidsvector langs de rotatie-as, ofwel de X
of Y
as. Bovendien kan een twee-bij-twee matrix worden opgebouwd uit twee orthogonale eenheidsvectoren, aangezien elke vector direct in de rijen kan worden ingevoegd. Hoewel deze constructiemethode een beetje ongewoon is voor 2D-fysica-engines, kan het nog steeds erg handig zijn om te begrijpen hoe rotaties en matrices in het algemeen werken.
Deze constructeur kan er ongeveer zo uitzien:
Mat22 :: Mat22 (const Vec2 & x, const Vec2 & y) m00 = x.x; m01 = x.y; m01 = y.x; m11 = y.y; // of Mat22 :: Mat22 (const Vec2 & x, const Vec2 & y) xCol = x; yCol = y;
Aangezien de belangrijkste bewerking van een rotatiematrix rotaties gebaseerd op een hoek is, is het belangrijk om een matrix vanuit een hoek te kunnen construeren en een vector met deze matrix te kunnen vermenigvuldigen (om de vector tegen de klok in te draaien met de hoek matrix werd geconstrueerd met):
Mat2 (echte radialen) real c = std :: cos (radians); real s = std: sin (radians); m00 = c; m01 = -s; m10 = s; m11 = c; // Roteer een vector const Vec2-operator * (const Vec2 & rhs) const return Vec2 (m00 * rhs.x + m01 * rhs.y, m10 * rhs.x + m11 * rhs.y);
Omwille van de bondigheid zal ik niet afleiden waarom de rotatie matrix tegen de klok in de vorm is:
a = hoek cos (a), -sin (a) sin (a), cos (a)
Het is echter belangrijk om op zijn minst te weten dat dit de vorm van de rotatiematrix is. Zie de Wikipedia-pagina voor meer informatie over rotatiematrices.
gerelateerde berichtenHet is belangrijk om het verschil tussen model en wereldruimte te begrijpen. Modelruimte is het coördinatenstelsel dat lokaal is voor een fysische vorm. De oorsprong bevindt zich bij de COM en de oriëntatie van het coördinatensysteem is uitgelijnd met de assen van de vorm zelf.
Om een vorm in een wereldruimte te transformeren, moet deze worden geroteerd en vertaald. Rotatie moet eerst plaatsvinden, omdat rotatie altijd wordt uitgevoerd rond de oorsprong. Omdat het object zich in de modelruimte bevindt (oorsprong bij COM), roteert de rotatie rond de COM van de vorm. Rotatie zou optreden met een Mat22
Matrix. In de voorbeeldcode zijn de oriëntatiematrices van de naam u
.
Nadat de rotatie is uitgevoerd, kan het object vervolgens worden vertaald naar zijn positie in de wereld door toevoeging van vectoren.
Zodra een object zich in de wereldruimte bevindt, kan het vervolgens worden vertaald naar de modelruimte van een geheel ander object door middel van inverse transformaties. Inverse rotatie gevolgd door omgekeerde translatie wordt gebruikt om dit te doen. Dit is hoeveel wiskunde wordt vereenvoudigd tijdens botsingsdetectie!
Inverse transformatie (van links naar rechts) van de wereldruimte naar modelruimte van de rode polygoon.Zoals te zien is in de bovenstaande afbeelding, als de inverse transformatie van het rode object wordt toegepast op zowel de rode als de blauwe polygoon, kan een botsingsdetectietest worden teruggebracht tot de vorm van een AABB versus OBB-test, in plaats van complexe wiskunde te berekenen tussen twee georiënteerde vormen.
In veel van de broncode van het monster worden hoekpunten voortdurend om verschillende redenen van model naar wereld en van model naar model getransformeerd. U moet een goed begrip hebben van wat dit betekent om de detectiecode van de monsterbotsing te begrijpen.
In deze sectie presenteer ik snelle contouren van veelhoek- en cirkelbotsingen. Zie de voorbeeldbroncode voor gedetailleerdere implementatiedetails.
Laten we beginnen met de meest complexe botsingsdetectieroutine in deze volledige artikelreeks. Het idee om te controleren op een botsing tussen twee polygonen kan het beste (naar mijn mening) worden gedaan met de Separating Axis Theorem (SAT).
In plaats van het projecteren van de vlakken van elke polygoon op elkaar, is er echter een iets nieuwere en efficiëntere methode, zoals geschetst door Dirk Gregorius in zijn GDC-lezing van 2013 (dia's hier gratis beschikbaar).
Het eerste dat moet worden geleerd, is het concept van ondersteuningspunten.
Het steunpunt van een polygoon is de vertex die het verst is langs een gegeven richting. Als twee hoekpunten gelijke afstanden hebben in de gegeven richting, is een van beide beide aanvaardbaar.
Om een steunpunt te berekenen, moet het puntproduct worden gebruikt om een afgetekende afstand langs een gegeven richting te vinden. Aangezien dit heel eenvoudig is, zal ik een snel voorbeeld in dit artikel laten zien:
// Het uiterste punt langs een richting binnen een veelhoek Vec2 GetSupport (const Vec2 & dir) real bestProjection = -FLT_MAX; Vec2 bestVertex; voor (uint32 i = 0; i < m_vertexCount; ++i) Vec2 v = m_vertices[i]; real projection = Dot( v, dir ); if(projection > bestProjection) bestVertex = v; bestProjection = projectie; return bestVertex;
Het puntproduct wordt gebruikt op elke vertex. Het puntproduct vertegenwoordigt een ondertekende afstand in een bepaalde richting, dus de vertex met de grootste geprojecteerde afstand zou de top zijn om terug te keren. Deze bewerking wordt uitgevoerd in de modelruimte van de gegeven veelhoek binnen de voorbeeldmotor.
Door het concept van steunpunten te gebruiken, kan een zoekactie naar de scheidingsas worden uitgevoerd tussen twee polygonen (veelhoek A en veelhoek B). Het idee van deze zoekopdracht is om langs alle vlakken van polygoon A te lopen en het steunpunt in de negatieve normaal van dat gezicht te vinden.
In de bovenstaande afbeelding worden twee ondersteuningspunten getoond: één op elk object. De blauwe normaal zou overeenkomen met het steunpunt op de andere veelhoek als de vertex het verst in de tegenovergestelde richting van de blauwe normaal. Op dezelfde manier zou de rode normaal worden gebruikt om het steunpunt aan het einde van de rode pijl te vinden.
De afstand van elk steunpunt tot het huidige vlak zou de getekende penetratie zijn. Door de grootste afstand op te slaan kan een mogelijke minimale penetratieas worden geregistreerd.
Hier is een voorbeeldfunctie van de voorbeeldbroncode die de mogelijke as van minimale penetratie vindt met behulp van de Krijg ondersteuning
functie:
real FindAxisLeastPenetration (uint32 * faceIndex, PolygonShape * A, PolygonShape * B) real bestDistance = -FLT_MAX; uint32 bestIndex; voor (uint32 i = 0; i < A->m_vertexCount; ++ i) // Haal een normaal gezicht op uit A Vec2 n = A-> m normals [i]; // Haal het ondersteuningspunt op van B langs -n Vec2 s = B-> GetSupport (-n); // Haal vertex op gezicht van A op, transformeer in // B's modelruimte Vec2 v = A-> m_vertices [i]; // Compute-penetratieafstand (in de modelruimte van B) real d = Dot (n, s - v); // Bewaar de grootste afstand als (d> bestDistance) bestDistance = d; bestIndex = i; * faceIndex = bestIndex; return bestDistance;
Aangezien deze functie de grootste penetratie retourneert, als deze penetratie positief is, betekent dit dat de twee vormen elkaar niet overlappen (negatieve penetratie zou geen scheidingsas betekenen).
Deze functie moet twee keer worden aangeroepen, waarbij A- en B-objecten elke oproep worden omgedraaid.
Vanaf hier moeten het incident en het referentievlak worden geïdentificeerd en moet het gezicht van het incident worden geknipt tegen de zijvlakken van het referentievlak. Dit is een nogal niet-triviale operatie, hoewel Erin Catto (maker van Box2D, en alle natuurkunde die momenteel door Blizzard wordt gebruikt) een aantal geweldige dia's heeft gemaakt over dit onderwerp in detail.
Deze clipping zal twee potentiële contactpunten genereren. Alle contactpunten achter het referentievlak kunnen als contactpunten worden beschouwd.
Behalve de dia's van Erin Catto heeft de voorbeeldengine ook de cliproutines geïmplementeerd als voorbeeld.
De botsingsroutine van cirkel versus polygoon is een stuk eenvoudiger dan polygoon versus polygoonbotsingsdetectie. Eerst wordt het dichtstbijzijnde vlak op de polygoon tot het midden van de cirkel op dezelfde manier berekend als het gebruik van steunpunten uit de vorige sectie: door een lus over elk vlak loodrecht op de veelhoek en de afstand van het midden van de cirkel tot het vlak te vinden.
Als het midden van de cirkel zich achter dit dichtstbijzijnde vlak bevindt, kan specifieke contactinformatie worden gegenereerd en kan de routine onmiddellijk worden beëindigd.
Nadat het dichtstbijzijnde gezicht is geïdentificeerd, gaat de test over in een lijnsegment versus cirkelproef. Een lijnsegment heeft drie interessante regio's genaamd Voronoi-regio's. Bestudeer het volgende diagram:
Voronoi-regio's van een lijnsegment.Intuïtief, afhankelijk van waar het midden van de cirkel zich bevindt, kunnen verschillende contactgegevens worden afgeleid. Stel je voor dat het midden van de cirkel zich op een van beide vertex-gebieden bevindt. Dit betekent dat het dichtstbijzijnde punt van het middelpunt van de cirkel een hoekvertex is en dat de juiste botsingsnormaal een vector is van deze top naar het cirkelmidden.
Als de cirkel zich binnen het gezichtsveld bevindt, wordt het midden van de cirkel op het segment met het dichtstbijzijnde punt van het segment tot het midden van de cirkel geprojecteerd. De normale botsing is gewoon het gezicht normaal.
Om te berekenen in welk Voronoi-gebied de cirkel ligt, gebruiken we het puntproduct tussen een aantal hoekpunten. Het idee is om een denkbeeldige driehoek te maken en te testen om te zien of de hoek van de hoek geconstrueerd met de top van het segment hoger of lager is dan 90 graden. Eén driehoek wordt gemaakt voor elke hoek van het lijnsegment.
Projecterende vector van randvertex tot cirkel midden op de rand.Een waarde van meer dan 90 graden betekent dat een randgebied is geïdentificeerd. Als de hoeken van de hoekverzenx van beide driehoeken niet boven de 90 graden liggen, moet het midden van de cirkel op het segment zelf worden geprojecteerd om veelvuldige informatie te genereren. Zoals te zien is in de afbeelding hierboven, als de vector van de randvertex naar het cirkelcentrum met de randvector zelf negatief is, dan is het Voronoi-gebied waar de cirkel ligt binnen bekend.
Gelukkig kan het puntproduct worden gebruikt om een ondertekende projectie te berekenen, en dit teken is negatief als het boven de 90 graden is en positief als hieronder.
Het is weer zover: we zullen voor de derde en laatste keer terugkeren naar onze impulsresolutie. Inmiddels zou je je volledig comfortabel moeten voelen bij het schrijven van hun eigen resolutiecode die resolutieimpulsen, samen met wrijvingsimpulsen, moet berekenen en ook lineaire projectie kan uitvoeren om overgebleven penetratie op te lossen.
Rotatiecomponenten moeten worden toegevoegd aan zowel de wrijvings- als penetratie-resolutie. Sommige energie zal in de hoeksnelheid worden geplaatst.
Dit is onze impulsresolutie zoals we die hebben achtergelaten uit het vorige artikel over wrijving:
\ [Vergelijking 5: \\
j = \ frac - (1 + e) ((V ^ A - V ^ B) * t) \ frac 1 massa ^ A + \ frac 1 massa ^ B
\]
Als we rotatiecomponenten erin gooien, ziet de laatste vergelijking er als volgt uit:
\ [Vergelijking 6: \\
j = \ frac - (1 + e) ((V ^ A - V ^ B) * t) \ frac 1 massa ^ A + \ frac 1 massa ^ B + \ frac (r ^ A \ times t) ^ 2 I ^ A + \ frac (r ^ B \ times t) ^ 2 I ^ B
\]
In de bovenstaande vergelijking is \ (r \) weer een "radius", zoals in een vector van de COM van een object naar het contactpunt. Een meer diepgaande afleiding van deze vergelijking is te vinden op de site van Chris Hecker.
Het is belangrijk om te beseffen dat de snelheid van een bepaald punt op een object is:
\ [Vergelijking 7: \\
V '= V + \ omega \ times r
\]
De toepassing van impulsen verandert enigszins om rekening te houden met de rotatiecondities:
void Body :: ApplyImpulse (const Vec2 & impulse, const Vec2 & contactVector) velocity + = 1.0f / mass * impuls; angularVelocity + = 1.0f / inertia * Cross (contactVector, impulse);
Dit concludeert het laatste artikel van deze serie. Inmiddels zijn nogal wat onderwerpen behandeld, waaronder op impuls gebaseerde resolutie, veelvuldige generatie, wrijving en oriëntatie, alles in twee dimensies.
Als je zover bent gekomen, moet ik je feliciteren! Het programmeren van physics-engines voor games is een uiterst moeilijk studiegebied. Ik wens alle lezers geluk en nogmaals, voel je vrij om commentaar te geven of vragen hieronder te stellen.