Simuler Tearable Cloth and Ragdolls Med Simple Verlet Integration

Den myke kroppsdynamikken handler om å simulere realistiske deformerbare objekter. Vi bruker den her for å simulere en tårbar klut gardin og et sett med ragdolls som du kan samhandle med og flirte rundt på skjermen. Det blir raskt, stabilt og enkelt nok til å gjøre med videregående matematikk.

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


Endelig resultatforhåndsvisning

I denne demoen kan du se et stort gardin (viser stoffsimuleringen), og en rekke små stickmen (som viser ragdoll-simuleringen):

Du kan også prøve demoen selv. Klikk og dra for å samhandle, trykk 'R' for å tilbakestille, og trykk 'G' for å bytte tyngdekraften.


Trinn 1: Et punkt og dets bevegelse

Byggeklossene i spillet vårt er punktet. For å unngå tvetydighet, kaller vi det PointMass. Detaljer er i navnet: det er et punkt i rommet, og det representerer en masse masse.

Den mest grunnleggende måten å implementere fysikk på for dette punktet, er å "forover" sin hastighet på en eller annen måte.

 x = x + velX y = y + velY

Trinn 2: Timesteps

Vi kan ikke anta at spillet vårt vil kjøre med samme hastighet hele tiden. Det kan kjøre med 15 bilder per sekund for noen brukere, men på 60 for andre. Det er best å ta hensyn til rammene for alle områder, som kan gjøres ved hjelp av et tidsrom.

 x = x + velX * timeElapsed y = y + velY * timeElapsed

På denne måten, hvis en ramme skulle ta lengre tid for å gå for en person enn for en annen, ville spillet fortsatt kjøre i samme hastighet. For en fysikkmotor er dette imidlertid utrolig ustabil.

Tenk deg om spillet ditt fryser for et sekund eller to. Motoren ville over-kompensere for det, og flytte PointMass forbi flere vegger og gjenstander som det ellers ville ha oppdaget kollisjon med. Dermed vil ikke bare kollisjonssensing bli påvirket, men også metoden for begrensningsløsning vi skal bruke.

Hvordan kan vi ha stabiliteten til den første ligningen, x = x + velX, med konsistensen til den andre ligningen, x = x + velX * timeElapsed? Hva om kanskje vi kunne kombinere de to?

Det er akkurat det vi skal gjøre. Tenk på vår tid forløpt var 30. Vi kunne gjøre akkurat det samme som sistnevnte ligning, men med høyere nøyaktighet og oppløsning ved å ringe x = x + (velX * 5) seks ganger.

 elapsedTime = lastTime - currentTime lastTime = currentTime // tilbakestilt sistTime // legg til tid som ikke kunne brukes siste ramme forløptTime + = leftOverTime // dele det opp i biter på 16 ms timesteps = gulv (forløpt tid / 16) // lagringstid vi kunne ikke bruke til neste ramme. leftOverTime = forlengetTime - tidspunkter * 16 for (i = 0; i < timesteps; i++)  x = x + velX * 16 y = y + velY * 16 // solve constraints, look for collisions, etc. 

Algoritmen bruker her en fast tidstest som er større enn en. Den finner den forløpte tiden, bryter den opp i faste størrelse "biter", og skyver gjenværende tid til neste ramme. Vi kjører simuleringen litt etter litt for hver bit vår forlenget tid er brutt opp i.

Jeg valgte 16 for timestepstørrelsen, for å simulere fysikken som om den kjørte på omtrent 60 bilder per sekund. Konvertering fra elapsedTime til rammer per sekund kan gjøres med litt matte: 1 sekund / forløptTimeInSekunder.

1s / (16ms / 1000s) = 62.5fps, så en 16ms timestep svarer til 62,5 bilder per sekund.


Trinn 3: Begrensninger

Begrensninger er begrensninger og regler lagt til simuleringen, veiledende hvor PointMasses kan og ikke kan gå.

De kan være enkle som denne begrensningsbegrensningen, for å hindre PointMasses å bevege seg fra venstre kant av skjermen:

 hvis (x < 0)  x = 0 if (velX < 0)  velX = velX * -1  

Å legge til begrensningen for høyre kant av skjermen gjøres på samme måte:

 hvis (x> bredde) x = bredde hvis (velX> 0) velX = velX * -1

Å gjøre dette for y-aksen er et spørsmål om å endre hver x til en y.

Å ha den riktige typen begrensninger kan resultere i meget vakre og fengslende interaksjoner. Begrensninger kan også bli svært komplekse. Prøv å forestille seg å simulere en vibrerende kurv med korn uten at kornet skjærer, eller en 100-ledd robotarm, eller til og med noe enkelt som en bunke med bokser. Den typiske prosessen innebærer å finne punkter av kollisjoner, finne den eksakte kollisjonstiden, og deretter finne den rette kraften eller impulsen til å søke på hver kropp for å hindre kollisjonen.

Å forstå hvor mye kompleksitet et sett av begrensninger kan ha kan være vanskelig, og deretter løse de begrensningene, i virkeligheten er enda vanskeligere. Hva vi skal gjøre, er å forenkle begrensningsløsningen betydelig.


Trinn 4: Verlet Integrasjon

En matematiker og programmør kalt Thomas Jakobsen utforsket noen måter å simulere fysikk av tegn til spill. Han foreslo at nøyaktigheten ikke er like viktig som troverdighet og ytelse. Hjertet i hele sin algoritme var en metode som ble brukt siden 60-årene til å modellere molekylær dynamikk, kalt Verlet Integrasjon. Du er kanskje kjent med spillet Hitman: Kodenavn 47. Det var et av de første spillene som brukte ragdollfysikk, og bruker algoritmen Jakobsen utviklet.

Verlet Integration er metoden vi skal bruke til å videresende posisjonen til PointMass. Hva vi gjorde tidligere, x = x + velX, er en metode som heter Euler Integration (som jeg også brukte i Koding Destructible Pixel Terrain).

Den store forskjellen mellom Euler og Verlet Integration er hvordan hastigheten implementeres. Ved bruk av Euler lagres en hastighet med objektet og legges til objektets posisjon hver ramme. Bruk av Verlet bruker imidlertid treghet ved å bruke forrige og nåværende posisjon. Ta forskjellen i de to posisjonene, og legg den til den siste posisjonen for å bruke treghet.

 // Inertia: gjenstander i bevegelse holdes i bevegelse. velX = x - lastX velY = y - lastY nextX = x + velX + accX * timestepSq nesteY = y + velY + akkY * timestepSq lastX = x lastY = y x = nextX y = nextY

Vi har lagt til akselerasjon der for tyngdekraften. Annet enn det, accX og accY vil ikke være nødvendig for å løse for kollisjoner. Ved hjelp av Verlet Integration, trenger vi ikke lenger å gjøre noen form for impuls eller tvingende løsning for kollisjoner. Å endre posisjonen alene vil være nok til å ha en stabil, realistisk og rask simulering. Det som Jakobsen utviklet er en lineær erstatning for noe som ellers ikke er lineært.


Trinn 5: Koblingsbegrensninger

Fordelene ved Verlet Integration kan best vises gjennom eksempel. I en tekstilmotor har vi ikke bare PointMasses, men også koblinger i mellom dem. Våre "lenker" vil være en avstandsbegrensning mellom to PointMasses. Ideelt sett ønsker vi to PointMasses med denne begrensningen å alltid være i en viss avstand fra hverandre.

Når vi løser denne begrensningen, bør Verlet Integration opprettholde disse punktmassene. For eksempel, hvis den ene enden skulle flyttes raskt ned, bør den andre enden følge den som en pisk gjennom tröghet.

Vi trenger bare en kobling for hvert par PointMasses som er festet til hverandre. Alle dataene du trenger i linken er PointMasses og hvilestrengene. Eventuelt kan du ha stivhet, for mer av en vårbegrensning. I vår demo har vi også en "tårefølsomhet", som er avstanden der linken vil bli fjernet.

Jeg vil bare forklare restingDistance her, men tåreavstanden og stivheten er både implementert i demoen og kildekoden.

 Link restingDistance tearDistance stiffness PointMass A PointMass B løse () math for å løse avstand

Du kan bruke lineær algebra for å løse for begrensningen. Finn avstandene mellom de to, avgjøre hvor langt langs restingDistance de er, så oversetter dem basert på det og deres forskjeller.

 // beregne avstanden diffX = p1.x - p2.x diffY = p1.y - p2.yd = sqrt (diffX * diffX + diffY * diffY) // forskjell skalar forskjell = (restingDistance - d) / d // oversettelse for hver PointMass. De vil bli presset 1/2 den nødvendige avstanden for å matche sine hvilesteder. translateX = diffX * 0.5 * forskjell translateY = diffY * 0.5 * forskjell p1.x + = translateX p1.y + = translateY p2.x - = translateX p2.y - = translateY

I demonstrasjonen tar vi også hensyn til masse og stivhet. Det er noen problemer med å løse denne begrensningen. Når det er mer enn to eller tre PointMasses knyttet til hverandre, kan løse noen av disse begrensningene krenke andre begrensninger som tidligere er løst.

Thomas Jakobsen møtte også dette problemet. I begynnelsen kan man lage et system av ligninger, og løse alle begrensningene samtidig. Dette vil imidlertid øke kompleksiteten raskt, og det vil være vanskelig å legge til mer enn bare noen få koblinger til systemet.

Jakobsen utviklet en metode som kan virke dum og naiv først. Han opprettet en metode som heter "avslapning", der i stedet for å løse for begrensningen en gang, løser vi for det flere ganger. Hver gang vi gjentar og løser for koblingene blir settet av lenker nærmere og nærmere alle som løses.

Trinn 6: Ta med deg sammen

For å samle, her er hvordan vår motor fungerer i pseudokode. For et mer spesifikt eksempel, sjekk demo kildekoden.

 animationLoop numPhysicsUpdates = hvor mange vi kan passe inn i den forløpte tiden for (hver numPhysicsUpdates) // (med begrensningSolve er noen nummer 1 eller høyere. Jeg bruker vanligvis 3 for (hver begrensningSolve) for (hver lenkebegrensning) løse begrensning  // Endlink-begrensningsløsninger // End-begrensninger Oppdater fysikk // (bruk Verlet!) // End fysikkoppdatering Tegnepunkter og lenker

Trinn 7: Legg til et stoff

Nå kan vi konstruere stoffet selv. Å lage linkene bør være ganske enkelt: Link til venstre når PointMass ikke er den første på rad, og koble sammen når den ikke er den første i sin kolonne.

Demoen bruker en en-dimensjonal liste for å lagre PointMasses, og finner poeng å knytte til bruk x + y * bredde.

 // vi vil at y-løkken skal være på utsiden, så den skanner rad for rad i stedet for kolonne for kolonne for (hver y fra 0 til høyde) for (hver x fra 0 til bredde) new PointMass ved x, y // legg til venstre hvis (x! = 0) legger PM til siste PM i liste // legg til høyre hvis (y! = 0) legger PM til PM @ ((y - 1) * ( bredde + 1) + x) i listen hvis (y == 0) pin PM legger til PM til listen

Du kan legge merke til i koden som vi også har "pin PM". Hvis vi ikke vil at våre gardiner skal falle, kan vi låse den øverste raden av PointMasses til startposisjonene. For å programmere en pinbegrensning, legg til noen variabler for å holde oversikt over stiftposisjonen, og flytt deretter PointMass til den posisjonen etter hver begrensning løse.


Trinn 8: Legg til noen Ragdolls

Ragdolls var Jakobsens opprinnelige intensjoner bak bruk av Verlet Integration. Først begynner vi med hodene. Vi lager en sirkelbegrensning som bare vil påvirke grensen.

 Sirkel PointMass-radius løse () hvis (y < radius) y = 2*(radius) - y; if (y > høyde-radius) y = 2 * (høyde-radius) - y; hvis (x> bredde-radius) x = 2 * (bredde - radius) - x; hvis (x < radius) x = 2*radius - x;  

Deretter kan vi lage kroppen. Jeg la til hver kroppsdel ​​til noe nøyaktig samsvar med masse- og lengdeforholdene til en vanlig menneskekropp. Sjekk ut Body.pde i kildefilene for full detaljer. Å gjøre dette vil føre oss til et annet problem: Kroppen vil lett forstyrre seg i vanskelige former, og ser veldig urealistisk ut.

Det finnes flere måter å fikse dette på. I demonstrasjonen bruker vi usynlige og veldig ustabile lenker fra føttene til skulderen og bekkenet til hodet for å naturligvis presse kroppen inn i en mindre vanskelig rusting.

Du kan også lage fake-vinkelbegrensninger ved å bruke koblinger. La oss si at vi har tre PointMasses, med to knyttet til en i midten. Du kan finne en lengde mellom endene for å tilfredsstille en valgt vinkel. For å finne den lengden kan du bruke loven om kosiner.

 A = hvilestreng fra ende PointMass til sentrum PointMass B = hvilestreng fra andre PointMass til sentrum PointMass lengde = sqrt (A * A + B * B - 2 * A * B * cos (vinkel)) opprette kobling mellom ende PointMasses med lengde som hvilestrekning

Endre lenken slik at denne begrensningen bare vil gjelde når avstanden er mindre enn hviledistansen, eller hvis den er mer enn. Dette vil holde vinkelen ved midtpunktet fra å være for nær eller for langt, avhengig av hva du trenger.


Trinn 9: Flere dimensjoner!

En av de store tingene med å ha en helt lineær fysikkmotor er at det kan være en hvilken som helst dimensjon du vil ha. Alt gjort med x ble også gjort til en y-verdi, og kan derfor utelukkes til tre eller fire dimensjoner (jeg er ikke sikker på hvordan du vil gjøre det, skjønt!)

For eksempel, her er en lenkebegrensning for simuleringen i 3D:

 // beregne avstanden diffX = p1.x - p2.x diffY = p1.y - p2.y diffZ = p1.z - p2.zd = sqrt (diffX * diffX + diffY * diffY + diffZ * diffZ) // forskjell skalar forskjell = (restingDistance - d) / d // oversettelse for hver PointMass. De vil bli presset 1/2 den nødvendige avstanden for å matche sine hvilesteder. translateX = diffX * 0.5 * differanse translateY = diffY * 0.5 * forskjell translateZ = diffZ * 0.5 * forskjell p1.x + = translateX p1.y + = translateY p1.z + = translateZ p2.x - = translateX p2.y - = translateX p2.y - = translateY p2.z - = translateZ

Konklusjon

Takk for at du leste! Mye av simuleringen er tungt basert på Thomas Jakobsens Advanced Character Physics-artikkel fra GDC 2001. Jeg gjorde mitt beste for å fjerne de fleste kompliserte ting, og forenkle til det punktet som de fleste programmører vil forstå. Hvis du trenger hjelp, eller har kommentarer, kan du legge inn under.