Fysiikkaa peliohjelmoijille - Osa 2: Kiihtyvyys ja voimat
Realistinen fysiikan mallinnus on noussut realistisen grafiikan rinnalle tärkeäksi elementiksi peleissä. Tämä artikkelisarja esittelee fysiikan mallinnuksen perusteet 2D- ja 3D-avaruudessa. Keskitymme erityisesti kiinteiden kappaleiden mekaniikkaan. Tämä on artikkelisarjan toinen osa.
15.1.2005 julkaistun artikkelin on kirjoittanut markus.
1. Tasaisesti muuttuva liike #
Artikkelisarjan edellisessä osassa oletimme, että liike oli tasaista. Tämä ei kuitenkaan päde tositilanteessa vaan myös nopeus muuttuu ajan suhteen. Eli nopeudellakin on derivaatta ajan suhteen. Nopeuden aikaderivaattaa kutsutaan kiihtyvyydeksi (englanniksi acceleration) ja sitä merkitään symbolilla a. Lausetta ”nopeuden derivaatta on kiihtyvyys” voidaan näin ollen merkitä v'(t)=a. Koska nopeus on sijainnin derivaatta ja kiihtyvyys nopeuden, on kiihtyvyys sijainnin toinen derivaatta. Tätä merkitään: s''(t)=a. Kiihtyvyyden yksikkö on metriä sekunti toiseen (lyhenne m/s^2). Kiihtyvyys on nopeuden tapaan vektori, jonka suunta esittää kiihtyvyyden suuntaa ja pituus suuruutta.
Vastaavasti kulmanopeuden derivaattaa kutsutaan kulmakiihtyvyydeksi (englanniksi angular acceleration). Kulmakiihtyvyyden symboli on α ja yksikkö radiaania sekunti toiseen (lyhenne rad/s^2). Jos se nyt sattuu jota kuta kiinnostamaan, niin vastaavat kaavat kulmakiihtyvyydelle olisivat ω’(t)=α ja φ’’(t)=α. 2D-avaruudessa kulmakiihtyvyys on paljas luku ja 3D-avaruudessa kolme komponenttinen vektori. Siis samoin kuin kulmanopeuden tapauksessa. Jos lisäisimme kiihtyvyyden ja kulmakiihtyvyyden rakenteisiin niistä tulisi seuraavan näköiset.
typedef struct
{
int shape; // Muoto. 0=ympyrä ja 1=suorakulmio
float radius[2]; // ”Säde” metreinä.
float position[2]; // Sijainti metreinä
float orientation; // Asento radiaaneina
float velocity[2]; // Nopeus metreinä sekunnissa
float acceleration[2]; // Kiihtyvyys
float angularAcceleration; // Kulmakiihtyvyys
} RIGIDBODY2D;
typedef struct
{
int shape; // Muoto. 0=pallo ja 1=laatikko
float radius[3]; // ”Säde” metreinä.
float position[3]; // Sijainti metreinä
float orientation[4]; // Asento kvaterniona
float velocity[3]; // Nopeus metreinä sekunnissa
float acceleration[3]; // Kiihtyvyys
float angularAcceleration[3]; // Kulmakiihtyvyys
} RIGIDBODY3D;
Oletetaan aluksi, että kiihtyvyys on vakio ja muutetaan artikkelisarjan edellisessä osassa tehtyä simulointifunktiota ottamaan kiihtyvyys huomioon. Koska tällä kertaa myös nopeus ja kulmanopeus muuttuvat, pitää nekin käyttää integraattorin läpi eli kopioimme ne alkuarvotaulukkoon muiden perään.
void simulate2D(RIGIDBODY2D *b, float t)
{
static float initialValue[6];
static float output[6];
static float temp[6*6];
// Kopioi nykyinen sijainti, asento,
// nopeus ja kulmanopeus alkuarvoksi.
initialValue[0]=b->position[0];
initialValue[1]=b->position[1];
initialValue[2]=b->orientation;
initialValue[3]=b->velocity[0];
initialValue[4]=b->velocity[1];
initialValue[5]=b->angularVelocity;
// Integroi aika-askeleen verran. Koska ”derive” tarvitsee
// kappaleen kiihtyvyyden, Annamme osoittimen kappaleeseen
// ”userData” -parametrina.
integrate(output, initialValue, 6, t, derive,
temp, (void *)b);
// Kopioi tulos uudeksi sijainniksi, asennoksi,
// nopeudeksi ja kulmanopeudeksi
b->position[0]=output[0];
b->position[1]=output[1];
b->orientation=output[2];
b->velocity[0]=output[3];
b->velocity[1]=output[4];
b->angularVelocity=output[5];
}
Entäpä “derive” -funktio? Ei kummempia. Toteamme vain sen tosiasian, että nopeuden derivaatta on kiihtyvyys ja kulmanopeuden kulmakiihtyvyys. On kuitenkin huomioitava, että nopeuksia ei voida lukea kappaleesta ”userData” -parametrin kautta, vaan ne pitää lukea funktion saaman ”value” -parametrin kautta. Tämä tietenkin siksi, että jos integraattori kutsuu ”derive” -funktiota jollain muulla x:n arvolla kuin 0, ei nopeus muuttumisen takia enää ole sama kuin alkutilanteessa. Onneksi integraattori kuitenkin toimittaa uudet arvot value parametrin kautta, joten voimme lukea nopeuden sieltä.
void derive( float *result,
const float *value,
float x, void *userData )
{
// Osoitin kappaleeseen saatiin “userData”:n kautta
RIGIDBODY2D *b=(RIGIDBODY2D *)userData;
// Sijainnin derivaatta on nopeus.
result[0]=value[3];
result[1]=value[4];
// Asennon derivaatta on kulmanopeus
result[2]=value[5];
// Nopeuden derivaatta on kiihtyvyys
result[3]=b->acceleration[0];
result[4]=b->acceleration[1];
// Kulmanopeuden derivaatta on kulmakiihtyvyys
result[5]=b->angularAcceleration;
}
Tässä olikin kaikki tarvittava tasaisesti muuttuvasta liikkeestä. Todellisuudessa kiihtyvyyskään ei ole vakio, joten jatkakaamme.
2. Voima #
Myös kiihtyvyys voi muuttua ajan suhteen. Emme kuitenkaan enää jatka derivointiketjua vaan etsimme syytä kiihtyvyydelle. Kiihtyvyyden voi aiheuttaa voima (englanniksi force) ja ainoastaan voima. Voiman symboli on F ja yksikkö on newton (lyhenne n). Voima on vektori, jonka suunta ilmaisee voiman suunnan ja pituus suuruuden. Kun voima vaikuttaa kappaleeseen, antaa se kappaleelle kiihtyvyyttä. Kappale ei kuitenkaan halua muuttaa liiketilaansa suosiolla vaan pistää hanttiin. Se kuinka hyvin kappale vastustaa voimaa riippuu kappaleesta. Tätä ominaisuutta vastustaa liiketilan muutosta kutsutaan hitausmassaksi tai lyhemmin vain massaksi (englanniksi mass). Massan yksikkö on kilogramma (lyhenne kg) ja symboli m. Massan olemassa oloa ei tiede vielä tänäkään päivänä osaa kunnolla selittää, mutta meidän ei tarvitse osata selittää sitä, vaan ainoastaan huomioida sen vaikutukset.
Voiman kappaleelle antama kiihtyvyys lasketaan seuraavalla Newtonin liikeyhtälöksi kutsutulla kaavalla:
a=F/m
Eli kiihtyvyys on voima jaettuna massalla. Kaavasta näkee myös sen, että ei ole olemassa kappaletta jonka massa on nolla, koska nollalla ei voi jakaa. Toinen huomattava asia on, että jos olisi olemassa kappale, jonka massa on ääretön, ei se koskaan saisi kiihtyvyyttä. Tällainen kappale olisi varsin kätevä, jos haluaisimme kappaleen johon voimat eivät vaikuta. Koska a=F/m=1/m*F olisi kätevämpää tallentaa massan sijasta sen käänteisluku 1/m. Tällöin massaa ei koskaan pysty asettamaan arvoon nolla ja ääretöntä massaa voisi esittää asettamalla sen käänteisluvuksi nolla. Täydennetäänpä rakenne massan käänteisluvulla. Lisäksi voimme poistaa kiihtyvyyden rakenteesta, koska se voidaan aina tarvittaessa laskea voimista ja massasta.
typedef struct
{
int shape; // Muoto. 0=ympyrä ja 1=suorakulmio
float radius[2]; // ”Säde” metreinä.
float position[2]; // Sijainti metreinä
float orientation; // Asento radiaaneina
float velocity[2]; // Nopeus metreinä sekunnissa
float inverseMass; // 1/massa kilogrammoina
} RIGIDBODY2D;
Maailmankaikkeudessa on neljä erilaista voimaa. Ne ovat: vahva ydinvoima, heikko ydinvoima, painovoima ja sähkömagneettinen voima. Kaksi ensimmäistä vaikuttavat (nimensä mukaisesti) ainoastaan atomin ytimen sisällä, joten niitä ei tarvitse ottaa simulaatioissa huomioon. Kaksi muuta sen sijaan pitää ottaa.
2.1 Painovoima
Kaikki aineelliset kappaleet vetävät toisiaan puoleensa. Voimaa, jolla aine vetää ainetta puoleensa kutsutaan painovoimaksi (englanniksi gravity). Painovoiman saa aikaan kappaleen ominaisuus, jota kutsutaan gravitaatiomassaksi. Gravitaatiomassaakin kutsutaan lyhemmin vain massaksi. Sitä ovatko gravitaatiomassa ja hitausmassa sama asia ei tiedetä. Yleisesti kuitenkin uskotaan näin olevan, joten talletamme rakenteeseemme vain yhden massan.
Kun kahden kappaleen massat ja keskipisteiden välinen etäisyys tiedetään, voidaan niiden välinen painovoima laskea kaavalla:
F=G*((m1*m2)/r^2)
Jossa m1 ja m2 ovat kappaleiden massat, r niiden keskipisteiden välinen etäisyys ja G gravitaatiovakio, jonka arvo on noin 0,0000000000667259. Koska G on hyvin pieni luku, täytyy kappaleiden massojen olla todella suuret ennen kuin niiden välinen painovoima kasvaa merkittäväksi. Tämän takia painovoima voidaan huoletta jättää huomiotta kaikille planeettaa pienemmille kappaleille.
Valitettavasti suoraan jalkojemme alla sijaitsee yksi planeetta, nimittäin Maa. Koska kaikki normaalit kappaleet ovat enemmän tai vähemmän lähellä Maan pintaa ja Maa on valtavan suuri verrattuna kaikkiin normaaleihin kappaleisiin, voidaan olettaa, että Maan painovoima antaa kaikille kappaleille suurin piirtein samanlaisen kiihtyvyyden. Tätä kiihtyvyyttä kutsutaan putoamiskiihtyvyydeksi ja merkitään yleensä symbolilla g. Putoamiskiihtyvyyden arvo on noin 9.81 (alaspäin). Ei siis tarvitse laskea maan aiheuttamaa painovoimaa ja siitä kiihtyvyyttä, vaan voidaan olettaa kiihtyvyydeksi 9.81 riippumatta kappaleesta. Tämä helpottaa laskelmia hieman.
2.2 Sähkömagneettinen voima
Jos kappaleilla on sähkövaraus niiden välillä vallitsee sähkömagneettinen voima.
Sähkömagneettinen voima lasketaan kaavalla:
F=k*(Q1*Q2)/r^2
Tässä Q1 ja Q2 ovat kappaleiden sähkövaraukset ja k sähkömagneettivakio, jonka arvo on noin 8987550000. Sähkömagneettivakio on huomattavasti suurempi kuin gravitaatiovakio, joten sähkömagneettinen voimakin on valtavan paljon vahvempi. Sähkömagneettinen voima on kuitenkin erittäin harvinainen, sillä kappaleilla ei yleensä ole sähkövarausta.
Sähkövaraus on yleinen alkeishiukkasilla ja molekyyleillä. Näin ollen sähkömagneettinen voima pitää yhdessä atomeja ja molekyylejä. Se siis pitää aineen kasassa. Tämän takia sähkömagneettinen voima ilmenee muunlaisina voimina, vaikuttamalla molekyylien välisiin sidoksiin. Tällaisia voimia ovat mm. jousivoima ja kitkavoima.
2.3 Jousivoima
Jousi on kappale, joka voi venyä ja venytyksen jälkeen pyrkii palautumaan alkuperäiseen pituuteensa. Tällaisia kappaleita ovat mm. kuminauha ja auton iskunvaimennin. Venytettynä jousi kohdistaa kumpaankin päähänsä voiman, joka lasketaan kaavalla:
F=-kx
Jossa k on jousivakio ja x jousen pituuden ja alkuperäisen pituuden erotus. Jousivakio ei ole mikään universaali vakio vaan se riippuu jousesta. Mitä suurempi vakio, sitä hankalampi jousta on venyttää.
2.4 Väliaineen vastus
Jos kappale liikkuu väliaineessa, kuten ilmassa tai vedessä vaikuttaa siihen väliaineen vastus voima. Väliaineen vastukselle voidaan esittää kaksi erilaista kaavaa riippuen siitä aiheuttaako kappaleen liike väliaineeseen pyörteitä. Jos pyörteitä ei esiinny lasketaan voima kaavalla:
F=-k*v
Jos taas pyörteitä esiintyy, lasketaan vastusvoima kaavalla:
F=-k*v^2
Kaavoissa k on jokin väliaineesta riippuva vakio. Itse asiassa tämä vakio riippuu myös kappaleen muodosta, mutta jätettäköön se huomiotta, joten voit olettaa, että k on kaikille kappaleille sama.
2.5 Esimekki
Katsotaanpa millaiselta "derive" -funktio näyttäisi, jos kappaleeseen vaikuttaisi koko ajan yhden newtonin voima oikealle.
void derive( float *result,
const float *value,
float x, void *userData )
{
// Yhden newtonin voima oikealle
const float F[2]={ 1, 0 };
// Osoitin kappaleeseen saatiin “userData”:n kautta
RIGIDBODY2D *b=(RIGIDBODY2D *)userData;
// Sijainnin derivaatta on nopeus.
result[0]=value[3];
result[1]=value[4];
// Asennon derivaatta on kulmanopeus
result[2]=value[5];
// Nopeuden derivaatta on kiihtyvyys,
// Joka lasketaan kaavasta F/m eli m^-1*F
result[3]=b->inverseMass*F[0];
result[4]=b->inverseMass*F[1];
// Jätetään kulmanopeus toistaiseksi rauhaan
result[5]=0;
}
Jos kappaleeseen vaikuttaa yhtä aikaa useita voimia, voidaan nämä voimat laskea raa'asti yhteen ja käyttää kokonaisvoimana saatua tulosta.
3. Vääntömomentti #
Tähän mennessä käsitellyt asiat koskivat vain kappaleen etenemisliikettä, ei sen pyörimisliikettä. Voima sai siis aikaan kiihtyvyyden. Mikähän saa aikaan kulmakiihtyvyyden? Kulmakiihtyvyyden saa aikaan vääntömomentti (englanniksi torque). Vääntömomentin yksikkö on newtonmetri (lyhenne Nm) ja symboli M. Etenevän liikkeen tavoin kappale vastustaa myös pyörimisliikkeen muutosta. Tätä ominaisuutta vastustaa pyörimisliiketilan muutosta kutsutaan hitausmomentiksi (englanniksi moment of inertia). Hitausmomentin symboli on J ja yksikkö kilogrammaa neliömetrille (lyhenne kgm^2). Kappaleen hitausmomentti riippuu kahdesta asiasta: kappaleen massasta ja muodosta. Itse asiassa hitausmomentti on ensimmäinen asia (piirtämisen lisäksi), johon varsinaisesti tarvitsemme kappaleen muotoa.
Kaikki kappaleet koostuvat atomeista, jotka ovat klassisessa fysiikassa aineen pienimpiä mahdollisia osasia. Atomin hitausmomentti lasketaan kaavalla:
atomin_hitausmomentti=atomin_massa*atomin_etäisyys_kappaleen_keskipisteestä ^ 2
Kappaleen hitausmomentti saadaan laskettua laskemalla yhteen sen kaikkien atomien hitausmomentit. Ongelmaksi kuitenkin muodostuu se, että atomeita on liian paljon käsiteltäväksi ja emme edes voi mitenkään tietää niiden kaikkien sijaintia. Onneksi kuitenkin kappaleille, jotka ovat säännöllisenmuotoisia ja joiden atomit ovat jakautuneet tasaisesti koko kappaleen alalle, voidaan muodostaa kaavat hitausmomentin laskemiseksi. Koska tässä artikkelisarjassa käytimme vain laatikoita ja palloja riittää, että annan valmiit kaavat näille kahdelle kappaleelle. En ota kantaa siihen mistä nämä kaavat tulevat.
2D-avaruudessa hitausmomentti paljas luku. Samoin kuin asento ja kulmanopeuskin eli aloitetaan 2D-tapauksesta.
Ympyrän, jonka massa on m ja säde r hitausmomentti on:
1/2*m*r^2.
Suorakulmion, jonka massa on m ja sivujen pituudet a ja b hitausmomentti on:
1/12*m*(a^2+b^2).
2D-avaruudessa hitausmomentti oli yksi paljas luku, koska pyöriminen tapahtuu aina saman "akselin" ympäri. 3D-avaruudessa pyörimisakseli voi vaihdella mielivaltaisesti, joten hitausmomenttikin muuttuu huomattavasti monimutkaisemmaksi. Kohta puolin sinun pitäisi viimeistään huomata kuinka valtavan paljon helpompaa kaikki on 2D-avaruudessa verrattuna 3D-avaruuteen.
3D-avaruudessa hitausmomentti on 3x3 matriisi. Matriisi tarkoittaa taulukkoa, mikäli et sitä vielä tiennyt. Eli 3x3 matriisi on taulukko, jossa on kolme riviä ja kolme saraketta. Matriisi voidaan helpointen tallentaa tietokoneella käyttämällä 9 komponenttista yksiulotteista taulukkoa, johon matriisin alkiot tallennetaan sarakkeittain. Seuraavassa on kaavat tämän matriisin muodostamiseksi pallolle ja laatikolle.
Pallon, jonka massa on m ja säde r hitausmomentti olisi:
Laatikon, jonka massa on m ja sivujen pituudet a, b ja c hitausmomentti olisi:
Tämä ei kuitenkaan vielä riitä! 3D-avaruudessa hitausmomentti muuttuu sitä mukaa kun kappale pyörii. On siis otettava asento huomioon. Hitausmomentista J voidaan laskea asentokvaternion φ avulla lopullinen asennon huomioon ottava hitausmomentti J2 kaavalla:
J2 = matriisiksi(φ) * J * matriisiksi(φ)^T
Kvaternio, jonka komponentit ovat w, x, y ja z muutetaan matriisiksi kaavalla:
Ja sama koodina:
void generateQuaternionMatrix(float m[9], const float q[4])
{
float xx=q[1]*q[1];
float xy=q[1]*q[2];
float xz=q[1]*q[3];
float xw=q[1]*q[0];
float yy=q[2]*q[2];
float yz=q[2]*q[3];
float yw=q[2]*q[0];
float zz=q[3]*q[3];
float zw=q[3]*q[0];
m[0] = 1 - 2 * ( yy + zz );
m[1] = 2 * ( xy + zw );
m[2] = 2 * ( xz - yw );
m[3] = 2 * ( xy - zw );
m[4] = 1 - 2 * ( xx + zz );
m[5] = 2 * ( yz + xw );
m[6] = 2 * ( xz + yw );
m[7] = 2 * ( yz - xw );
m[8] = 1 - 2 * ( xx + yy );
}
Merkintä "^T" taas tarkoittaa matriisin transpoosia, joka lasketaan pyöräyttämällä matriisi vinottaisakselinsa ympäri. Seuraavassa transponointi koodina.
void transposeMatrix(float m[9])
{
float temp;
DWORD i, j;
for (i=0; i<2; i++)
{
for (j=i+1; j<3; j++)
{
temp=m[j+3*i];
m[j+3*i]=m[3*j+i];
m[3*j+i]=temp;
}
}
}
Nyt on jäljellä enää matriisien kertolaskua. Kaksi 3x3 matriisia voidaan kertoa keskenään seuraavan kaavan mukaisesti.
Ideana oli siis kertoa rivit ja sarakkeet keskenään ja summata tulokset. Tässä sama koodina:
void glmMultMatrix3f(float result[9], const float m1[9], const float m2[9])
{
float sum;
int x, y, i;
for (x=0; x<3; x++)
{
for (y=0; y<3; y++)
{
sum=0;
for (i=0; i<3; i++) sum+=m1[y+3*i]*m2[3*x+i];
result[y+3*x]=sum;
}
}
}
Huh huh! Olipa monimutkaista. Aina kun tarvitsemme kappaleen hitausmomenttia muutamme kappaleen asennon matriisiksi ja laskemme asento*hitausmomentti*asennon_transpoosi, jolloin saamme oikean hitausmomentin.
Samoin kuin massankin tapauksessa, ei ole olemassa kappaletta, jonka hitausmomentti on nolla ja kappale, jonka hitausmomentti on ääretön, on immuuni vääntömomentille, joten tallennetaan hitausmomentin sijasta sen käänteisluku. 2D-avaruudessa tämä on helppoa, mutta 3D-avaruudessa pitää ottaa käänteismatriisi. 3x3 matriisin käänteismatriisi lasketaan seuraavalla kaavalla:
Kaavassa |M| tarkoittaa matriisin determinanttia, joka taas puolestaan lasketaan kaavalla
Seuraavassa samat koodina:
float determinant(const float m[9])
{
return
-m[6]*m[4]*m[2]+
m[3]*m[7]*m[2]+
m[6]*m[1]*m[5]-
m[0]*m[7]*m[5]-
m[3]*m[1]*m[8]+
m[0]*m[4]*m[8];
}
void generateInverseMatrix(float i[9], const float m[9])
{
float x=determinant(m);
if (x==0) return;
i[0]=(-m[7]*m[5]+m[4]*m[8])/x;
i[3]=( m[6]*m[5]-m[3]*m[8])/x;
i[6]=(-m[6]*m[4]+m[3]*m[7])/x;
i[1]=( m[7]*m[2]-m[1]*m[8])/x;
i[4]=(-m[6]*m[2]+m[0]*m[8])/x;
i[7]=( m[6]*m[1]-m[0]*m[7])/x;
i[2]=(-m[4]*m[2]+m[1]*m[5])/x;
i[5]=( m[3]*m[2]-m[0]*m[5])/x;
i[8]=(-m[3]*m[1]+m[0]*m[4])/x;
}
Täydennetään nyt rakenteet hitausmomentilla. 3x3 matriisi on tallennetaan 9 alkioisena taulukkona, johon matriisi tallennetaan sarakkeittain.
typedef struct
{
int shape; // Muoto. 0=ympyrä ja 1=suorakulmio
float radius[2]; // ”Säde” metreinä.
float position[2]; // Sijainti metreinä
float orientation; // Asento radiaaneina
float velocity[2]; // Nopeus metreinä sekunnissa
float inverseMass; // 1/massa kilogrammoina
float inverseMomentOfInertia; // 1/hitausmomentti
} RIGIDBODY2D;
typedef struct
{
int shape; // Muoto. 0=pallo ja 1=laatikko
float radius[3]; // ”Säde” metreinä.
float position[3]; // Sijainti metreinä
float orientation[4]; // Asento kvaterniona
float velocity[3]; // Nopeus metreinä sekunnissa
float inverseMass; // 1/massa kilogrammoina
float inverseMomentOfInertia[9]; // 1/hitausmomentti
} RIGIDBODY3D;
Kulmakiihtyvyys lasketaan vääntömomentista ja hitausmomentista kaavalla:
α=M/J
Eli kulmakiihtyvyys on vääntömomentti jaettuna hitausmomentilla. Huomaat varmaan yhtäläisyyden kaavaan a=F/m. Sama kaava pätee niin 2D- kuin 3D-avaruudessakin. 2D-avaruudssa kaikki olivat paljaita lukuja, joten laskeminen ei tuottane ongelmia. 3D-avaruudessa α ja M ovat kolme komponenttisia vektoreita ja J 3x3 matriisi, joten kaavan laskeminen vaatinee hieman selvitystä. Koska α=M/J=J^-1*M ja meillä sattui sopivasti olemaan J^-1 valmiina, tarvitsee vain kertoa 3x3 matriisilla 3 komponenttinen vektori. Tämä tehdään kaavalla:
Vielä sama koodina:
void multMatrixWithVector(float result[3], const float m[9], const float v[3])
{
result[0]=m[0]*v[0]+m[3]*v[1]+m[6]*v[2];
result[1]=m[1]*v[0]+m[4]*v[1]+m[7]*v[2];
result[2]=m[2]*v[0]+m[5]*v[1]+m[8]*v[2];
}
Maailmassa oli siis 4 perusvoimaa, joiden ilmentymiä kaikki voimat olivat. Onkohan maailmassa sitten 4 perusvääntömomenttiakin? Kenties painovääntömomentti ja sähkömagneettinen vääntömomentti? Ei ole. Vääntömomentti nimittäin johtuu aina voimasta. Voima aiheuttaa vääntömomentin, jos voima ei kohdistu kappaleen keskipisteeseen tai tasaisesti kappaleen kaikkiin pisteisiin. Voimista painovoiman, sähkömagneettisen voiman ja väliaineen vastuksen voidaan katsoa vaikuttavan kappaleen keskipisteeseen (tai tasaisesti sen kaikkiin pisteisiin). Sen sijaan jousivoima vaikuttaa siihen pisteeseen, jossa jousi on kiinni. Jos voima kohdistuu pisteeseen p ja kappaleen keskipiste sijaitsee pisteessä x, lasketaan voiman aiheuttama vääntömomentti kaavasta:
Koska 2D-tapauksessa vektorit olivat kaksikomponenttisia ja kahden vektorin ristitulon voi laskea vain kolme komponenttisilla vektoreilla, pitää kaksi komponenttisista vektoreista tehdä väkisin kolme komponenttisia. Tämä tehdään lisäämällä niihin yksi ylimääräinen komponentti, jonka arvoksi laitetaan nolla. Ristitulon laskemisen jälkeen vastaus saadaan tulosvektorin kolmannesta komponentista, kahden muun ollessa nolla.
Jos voimia on useita, lasketaan vääntömomentti jokaiselle voimalle erikseen ja sitten vääntömomentit lasketaan yhteen.
4. Vakausongelmat ja vaimennus #
Kuten jo artikkelisarjan ensimmäisessä osassa totesimme, numeerinen integraattori ei anna täydellisen oikeita tuloksia vaan ainoastaan likimääräisiä vastauksia. Tämän takia saattaa jostain syystä käydä niin, että integraattori antaa kokoajan aavistuksen verran liian isoja tuloksia. Tällöin virheet kasaantuvat seuraavan virheen ollen aina edellistä suurempi ja lopulta integraattori sylkee ulos äärettömyyksiä. Sanotaan, että integraattori "räjähtää". Tällainen tilanne, jos ei kaataisi, niin ainakin sotkisi simulaation. Jos taas integraattori antaisi koko ajan aavistuksen verran liian pieniä tuloksia, ei tilanne ole läheskään niin paha. Silloin kappaleen vauhti vain pikkuhiljaa pienenee.
Välttääksemme räjähtämisen pitää keksiä jotain tilanteen vakauttamiseksi. Yksi tällainen keino on vaimennus (englanniksi damping). Vaimennuksen idea on pienentää integraatorin antamia tuloksia aavistuksen, jolloin ne eivät ainakaan ole liian suuria. Tämä estää räjähtämisen, ellei integraattori ammu aivan metsään, kuten Eulerin menetelmä yleensä tekee. Haittapuolena taas on, että tämän seurauksena liike koko ajan hieman pienenee lopulta pysähtyen. Tämä ei kuitenkaan yleensä haittaa sillä kappaleiden liikkeen pysähtyminen vastaa havaintojamme oikean elämän liikkeestä, joka tosiaankin aina lopulta hiljenee. Oikeassa elämässä liikkeen hiljenemisen aiheuttaa kitkavoima, josta puhumme vasta artikkelisarjan viimeisessä osassa. Varsinkin väliaineen vastusta voi käyttää helposti vaimentimena.
Seuraavassa muutama sääntö pitämään virheet mahdollisimman pienenä.
1. Pidä aika askel pienenä. Integraattori antaa sitä tarkempia vastauksia, mitä pienemmillä x:n arvoilla sitä kutsutaan.
2. Pidä voimat pienenä. Suuret voimat aiheuttavat suuremman virheen kuin pienemmät.
3. Älä käytä Eulerin menetelmää!
Yksi hyvä keino simulaation vakauden tarkkailuun on energian säilymislaki. Se sanoo, että energiaa ei voi luoda tai hävittää ainoastaan muuttaa muodosta toiseen. Kappaleen energia voi olla kolmessa eri muodossa. Ne ovat liike-energia, pyörimisenergia ja potentiaalienergia.
Liike-energia lasketaan kaavalla: 1/2*m*v^2
Pyörimisenergia lasketaan kaavalla: 1/2*J*ω^2
Potentiaalienergiaa kappaleella on jokaista voimaa vastaan. Se riippuu siis voimista. Maan vetovoimaa vastaan oleva potentiaalienergia lasketaan kaavalla: mgh. h on kappaleen korkeus maasta. Sillä mistä pisteestä h mitataan, ei ole väliä, sillä emme tarvitse potentiaalienergian absoluuttista arvoa. Meidän täytyy ainoastaan tutkia pysyykö sen arvo muuttumattomana. Jousivoimaa vastaan potentiaalienergia lasketaan kaavalla: kx^2. Kaavassa k on jousivakio ja x jousen pituuden ja oikean pituuden erotus.
Kun lasket nämä kolme eri energiatyyppiä yhteen, pitäisi tuloksen olla koko ajan vakio. Jos näin ei ole antaa integraattori virheellisiä arvoja, kuten lähes aina käy.
Huomaa, että väliaineen vastus ja kitka muuttuvat energiaa lähinnä lämmöksi, jota ei tässä otettu huomioon, joten ne vähentävät energia. Ilman näitä voimia energian pitäisi kuitenkin säilyä.
5. Esimerkkiohjelma #
Tällä kertaa esimerkkiohjelmamme on jo aavistuksen verran edellistä kiinnostavampi. Esimerkkiohjelma demonstroi jousivoimia, painovoimaa ja niiden vaikutusta kappaleisiin. Simulaattorissa ”katosta” roikkuu kuminauhojen varassa muutamia kappaleita.
2D-esimerkkihjelman voit ladata tästä:
http://www.suomipelit.com/files/artikkel...
3D-esimerkkiohjelman voit ladata tästä:
http://www.suomipelit.com/files/artikkel...
6. Loppusanat #
Tässä osassa opit voimista ja niiden vaikutuksesta kappaleen liiketilaan. Seuraavassa osassa puhumme törmäyksistä.
Raportoithan kaikki tästä artikkelista löytämäsi virheet (niin kirjoitus-, kuin asiavirheetkin) osoitteeseen markus.ilmola@pp.inet.fi , niin korjaan ne mahdollisimman nopeasti. Myös kaikki kommentit ja kysymykset ovat tervetulleita.
Lopuksi linkkejä, joissa lisää luettavaa tähän mennessä opituista asioista.
Suomenkielinen selostus erilaisista voimista:
http://butler.cc.tut.fi/~lassila/johdatu...
David Baraffin vallan mainio artikkeli "Physically Based Modeling". Osa 1.
http://www-2.cs.cmu.edu/~baraff/sigcours...
Chriss Checkerin hieman suppeampi artikkeli. Osat 1 ja 2.
http://www.d6.com/users/checker/pdfs/gdm...
http://www.d6.com/users/checker/pdfs/gdm...










