Lekce 30

Lekce 30 - Detekce kolizφ

Na podobn² tutorißl jste u₧ jist∞ netrp∞liv∞ Φekali. NauΦφte se zßklady o detekcφch kolizφ, jak na n∞ reagovat a na fyzice zalo₧enΘ modelovacφ efekty (nßrazy, p∙sobenφ gravitace ap.). Tutorißl se vφce zam∞°uje na obecnou funkci kolizφ ne₧ zdrojov²m k≤d∙m. NicmΘn∞ d∙le₧itΘ Φßsti k≤du jsou takΘ popsßny. NeoΦekßvejte, ₧e po prvnφm p°eΦtenφ ·pln∞ vÜemu z kolizφ porozumφte. Je to komplexnφ nßm∞t, se kter²m vßm pomohu zaΦφt.

Zdrojov² k≤d, na n∞m₧ tato lekce stavφ, pochßzφ z mΘho d°φv∞jÜφho p°φsp∞vku do jednΘ sout∞₧e (najdete na OGLchallenge.dhs.org). TΘmatem byly BlßznivΘ kolize a m∙j p°φsp∞vek (mimochodem, zφskal prvnφ mφsto :-) se jmenoval Magickß mφstnost.

Detekce kolizφ jsou obtφ₧nΘ, dodnes nebylo nalezeno ₧ßdnΘ snadnΘ °eÜenφ. Samoz°ejm∞ existujφ velice obecnΘ algoritmy, kterΘ umφ pracovat s jak²mkoli druhem objekt∙, ale oΦekßvejte od nich takΘ pat°iΦnou cenu. My budeme zkoumat postupy, kterΘ jsou velmi rychlΘ, relativn∞ snadnΘ k pochopenφ a v rßmci mezφ celkem flexibilnφ. D∙raz musφ b²t vlo₧en nejen na detekci kolize, ale i na reakci objekt∙ na nßraz. Je d∙le₧itΘ, aby se vÜe d∞lo podle fyzikßlnφch zßkon∙. Mßme mnoho v∞cφ na prßci! Poj∩me se tedy podφvat, co vÜechno se v tΘto lekci nauΦφme:

Detekce kolizφ mezi

Fyzikßln∞ zalo₧enΘ modelovßnφ

Specißlnφ efekty

Zdrojov² k≤d se d∞lφ na p∞t Φßstφ

T°φdy Vektor, Ray a Matrix jsou velmi u₧iteΦnΘ. Dajφ se pou₧φt v jakΘmkoli projektu. DoporuΦuji je peΦliv∞ prostudovat, t°eba se vßm budou n∞kdy hodit.

Detekce kolizφ

Polop°φmka

P°i detekci kolizφ vyu₧ijeme algoritmus, kter² se v∞tÜinou pou₧φvß v trasovßnφ polop°φmek (ray tracing). Vektorovß reprezentace polop°φmky je tvo°ena bodem, kter² oznaΦuje zaΦßtek a vektorem (obyΦejn∞ normalizovan²m) urΦujφcφm sm∞r polop°φmky. Rovnice polop°φmky:

bod_na_polop°φmce = zaΦßtek + t * sm∞r

╚φslo t nab²vß hodnot od nuly do nekoneΦna. Dosadφme-li nulu zφskßme poΦßteΦnφ bod. U v∞tÜφch Φφsel dostaneme odpovφdajφcφ body na polop°φmce. Bod, poΦßtek i sm∞r jsou 3D vektory se slo₧kami x, y, a z. Nynφ m∙₧eme pou₧φt tuto reprezentaci polop°φmky k v²poΦtu pr∙seΦφku s rovinou nebo vßlcem.

Pr∙seΦφk polop°φmky a roviny

Vektorovß reprezentace roviny vypadß takto:

Xn dot X = d

Xn je normßla roviny, X je bod na jejφm povrchu a d je Φφslo, kterΘ urΦuje vzdßlenost roviny od poΦßtku sou°adnicovΘho systΘmu.

P°ekl.: Pod operacφ "dot" se skr²vß skalßrnφ souΦin dvou vektor∙ (dot product), kter² se vypoΦte souΦtem nßsobk∙ jednotliv²ch x, y a z slo₧ek. Nechßvßm ho v p∙vodnφm zn∞nφ, proto₧e i ve zdrojov²ch k≤dech budeme volat metodu dot().

P°ekl.: P dot Q = Px * Qx + Py * Qy + Pz * Qz

Abychom definovali rovinu, pot°ebujeme 3D bod a vektor, kter² je kolm² k rovin∞. Pokud vezmeme za 3D bod vektor (0, 0, 0) a pro normßlu vektor (0, 1, 0), protφnß rovina osy x a z. Pokud znßme bod a normßlu, dß se chyb∞jφcφ Φφslo d snadno dopoΦφtat.

Pozn.: Vektorovß reprezentace roviny je ekvivalentnφ vφce znßmΘ form∞ Ax + By + Cz + D = 0 (obecnß rovnice roviny). Pro p°epoΦet dosa∩te za A, B, C slo₧ky normßly a p°i°a∩te D = -d.

Nynφ mßme dv∞ rovnice

bod_na_polop°φmce = zaΦßtek + t * sm∞r

Xn dot X = d

Pokud polop°φmka protne rovinu v n∞jakΘm bod∞, musφ sou°adnice pr∙seΦφk∙ vyhovovat ob∞ma rovnicφm

Xn dot bod_na_polop°φmce = d

Nebo

(Xn dot zaΦßtek) + t * (Xn dot sm∞r) = d

Vyjßd°φme t

t = (d - Xn dot zaΦßtek) / (Xn dot sm∞r)

Dosadφme d

t = (Xn dot bod_na_polop°φmce - Xn dot zaΦßtek) / (Xn dot sm∞r)

Vytkneme Xn

t = (Xn dot (bod_na_polop°φmce - zaΦßtek)) / (Xn dot sm∞r)

╚φslo t reprezentuje vzdßlenost od zaΦßtku polop°φmky k pr∙seΦφku s rovinou ve sm∞ru polop°φmky (ne na kolmici). Dosazenφm t do rovnice polop°φmky zφskßme koliznφ bod. Existuje n∞kolik specißlnφch situacφ. Pokud Xn dot sm∞r = 0, budou tyto dva vektory navzßjem kolmΘ. Polop°φmka prochßzφ rovnob∞₧n∞ s rovinou a tudφ₧ neexistuje koliznφ bod. Pokud je t zßpornΘ, pr∙seΦφk le₧φ p°ed poΦßteΦnφm bodem. Polop°φmka nesm∞°uje k rovin∞, ale od nφ. Op∞t ₧ßdn² pr∙seΦφk.

int TestIntersionPlane(const Plane& plane, const TVector& position, const TVector& direction, double& lamda, TVector& pNormal)

{

double DotProduct = direction.dot(plane._Normal);// Skalßrnφ souΦin vektor∙

double l2;// UrΦuje koliznφ bod

if ((DotProduct < ZERO) && (DotProduct > -ZERO))// Je polop°φmka rovnob∞₧nß s rovinou?

return 0;// Bez pr∙seΦφku

l2 = (plane._Normal.dot(plane._Position - position)) / DotProduct;// Dosazenφ do vzorce

if (l2 < -ZERO)// Sm∞°uje polop°φmka od roviny?

return 0;// Bez pr∙seΦφku

pNormal = plane._Normal;// Normßla roviny

lamda = l2;// Koliznφ bod

return 1;// Pr∙seΦφk existuje

}

Pr∙seΦφk polop°φmky a vßlce

V²poΦet pr∙seΦφku polop°φmky s nekoneΦn∞ dlouh²m vßlcem je mnohem komplikovan∞jÜφ ne₧ vysv∞tlenφ toho, proΦ se tφm zde nebudeme zab²vat. Na pozadφ je p°φliÜ mnoho matematiky. M²m primßrnφm zßm∞rem je poskytnout a vysv∞tlit nßstroje bez zabφhßnφ do zbyteΦn²ch detail∙, kterΘ by stejn∞ n∞kte°φ nepochopili. Vßlec je tvo°en polop°φmkou, kterß reprezentuje jeho osu, a polom∞rem podstavy. Pro detekci kolize se v tomto tutorißlu pou₧φvß funkce TestIntersetionCylinder(), kterß vracφ jedniΦku, pokud byl nalezen pr∙seΦφk, jinak nulu.

int TestIntersionCylinder(const Cylinder& cylinder, const TVector& position, const TVector& direction, double& lamda, TVector& pNormal, TVector& newposition)

V parametrech se p°edßvß struktura vßlce, zaΦßtek a sm∞rov² vektor polop°φmky. Krom∞ nßvratovΘ hodnoty zφskßme z funkce vzdßlenost od pr∙seΦφku (na polop°φmce), normßlu vychßzejφcφ z pr∙seΦφku a bod pr∙seΦφku.

Kolize mezi dv∞ma pohybujφcφmi se koulemi

Koule je v geometrii reprezentovßna st°edem a polom∞rem. ZjiÜt∞nφ, jestli do sebe dv∞ koule narazily je banßlnφ. VypoΦteme vzdßlenost mezi dv∞ma st°edy a porovnßme ji se souΦtem polom∞r∙. Jak snadnΘ!

ProblΘmy nastanou p°i hledßnφ koliznφho bodu dvou POHYBUJ═C═CH se koulφ. Na obrßzku je p°φklad, kdy se dv∞ koule p°esunou z jednoho mφsta do druhΘho za urΦit² Φasov² ·sek. Jejich drßhy se protφnajφ, ale to nenφ dostateΦn² d∙vod k tvrzenφ, ₧e do sebe opravdu narazφ. Mohou se nap°φklad pohybovat rozdφlnou rychlostφ. Jedna tΘm∞° stojφ a druhß je za okam₧ik ·pln∞ n∞kde jinde.

Dv∞ pohybujφcφ se koule

V p°edchozφch metodßch jsme °eÜili rovnice dvou geometrick²ch objekt∙. Pokud podobnß rovnice pro dan² objekt neexistuje nebo nem∙₧e b²t pou₧ita (pohyb po slo₧itΘ k°ivce), pou₧φvß se jinß metoda. V naÜem p°φpad∞ znßme poΦßteΦnφ i koncovΘ body p°esunu, Φasov² krok, rychlost (+sm∞r) a metodu zjiÜt∞nφ nßrazu statick²ch koulφ. Rozkouskujeme Φasov² ·sek na malΘ Φßsti. Koule budeme v zßvislosti na nich postupn∞ posunovat a poka₧dΘ testovat kolizi. Pokud najdeme n∞kter² bod, kdy je vzdßlenost koulφ menÜφ ne₧ souΦet jejich polom∞r∙, vezmeme minulou pozici a oznaΦφme ji jako koliznφ bod. M∙₧e se jeÜt∞ zaΦφt interpolovat mezi t∞mito dv∞ma body na rozhranφ, kdy kolize jeÜt∞ nebyla a u₧ je, abychom naÜli ·pln∞ p°esnou pozici, ale v∞tÜinou to nenφ pot°eba.

╚φm menÜφ bude Φasov² ·sek, tφm budou Φßsti vzniklΘ rozsekßnφm menÜφ a metoda bude vφce p°esnß (a vφce nßroΦnß na hardware poΦφtaΦe). Nap°φklad, pokud bude Φasov² ·sek 1 a Φßsti 3, budeme zjiÜ¥ovat kolizi v Φasech 0, 0,33, 0,66 a 1. V nßsledujφcφm v²pisu k≤du hledßme koule, kterΘ b∞hem nßsledujφcφho ΦasovΘho kroku narazφ do kterΘkoli z ostatnφch. Funkce vrßtφ indexy obou koulφ, bod a Φas nßrazu.

int FindBallCol(TVector& point, double& TimePoint, double Time2, int& BallNr1, int& BallNr2)

{

TVector RelativeV;// Relativnφ rychlost mezi koulemi

TRay rays;// Polop°φmka

double MyTime = 0.0;// Hledßnφ p°esnΘ pozice nßrazu

double Add = Time2 / 150.0;// Rozkouskuje Φasov² ·sek na 150 Φßstφ

double Timedummy = 10000;// ╚as nßrazu

TVector posi;// Pozice na polop°φmce

// Test vÜech koulφ proti vÜem ostatnφm po 150 krocφch

for (int i = 0; i < NrOfBalls - 1; i++)// VÜechny koule

{

for (int j = i + 1; j < NrOfBalls; j++)// VÜechny zb²vajφcφ koule

{

// V²poΦet vzdßlenosti

RelativeV = ArrayVel[i] - ArrayVel[j];// Relativnφ rychlost mezi koulemi

rays = TRay(OldPos[i], TVector::unit(RelativeV));// Polop°φmka

if ((rays.dist(OldPos[j])) > 40)// Je vzdßlenost v∞tÜφ ne₧ 2 polom∞ry?

{

continue;// DalÜφ

}

// Nßraz

MyTime = 0.0;// Inicializace p°ed vstupem do cyklu

while (MyTime < Time2)// P°esn² bod nßrazu

{

MyTime += Add;// Zv∞tÜφ Φas

posi = OldPos[i] + RelativeV * MyTime;//P°esun na dalÜφ bod (pohyb na polop°φmce)

if (posi.dist(OldPos[j]) <= 40)// Nßraz

{

point = posi;// Bod nßrazu

if (Timedummy > (MyTime - Add))// Bli₧Üφ nßraz, ne₧ kter² jsme u₧ naÜli (v Φase)?

{

Timedummy = MyTime - Add;// P°i°adit Φas nßrazu

}

BallNr1 = i;// OznaΦenφ koulφ, kterΘ narazily

BallNr2 = j;

break;// UkonΦφ vnit°nφ cyklus

}

}

}

}

if (Timedummy != 10000)// NaÜli jsme kolizi?

{

TimePoint = Timedummy;// ╚as nßrazu

return 1;// ┌sp∞ch

}

return 0;// Bez kolize

}

Kolize mezi koulφ a rovinou nebo vßlcem

Nynφ u₧ umφme zjistit pr∙seΦφk polop°φmky a roviny/vßlce. Tyto znalosti pou₧ijeme pro hledßnφ kolizφ mezi koulφ a jednφm z t∞chto objekt∙. Pot°ebujeme najφt p°esn² bod nßrazu. P°evod znalostφ z polop°φmky na pohybujφcφ se kouli je relativn∞ snadn². Podφvejte se na lev² obrßzek, mo₧nß, ₧e podstatu pochopφte sami.

Nßraz pohybujφcφ se koule do roviny/vßlce

Ka₧dß koule sice mß polom∞r, ale my ji budeme brßt jako bezrozm∞rnou Φßsti, kterß mß pouze pozici. K povrchu t∞lesa p°iΦteme ve sm∞ru normßlovΘho vektoru offset urΦen² polom∞rem koule. Neboli k polom∞ru vßlce p°iΦteme pr∙m∞r koule (2 polom∞ry; z ka₧dΘ strany jeden). Operacφ jsme se vrßtili k detekci kolize polop°φmka - vßlec. Rovina je jeÜt∞ jednoduÜÜφ. Posuneme ji sm∞rem ke kouli o jejφ polom∞r. Na obrßzku jsou Φßrkovan∞ nakresleny "virtußlnφ" objekty pro testy kolizφ a pln∞ objekty, kterΘ program vykreslφ. Kdybychom k objekt∙m p°i testech nep°ipoΦφtßvali offset, koule by p°ed odrazem z poloviny pronikaly do objekt∙ (obrßzek vpravo).

Mßme-li urΦit mφsto nßrazu, je vhodnΘ nejd°φve zjistit, jestli kolize nastane p°i aktußlnφm ΦasovΘm ·seku. Proto₧e polop°φmka mß nekoneΦnou dΘlku, je v₧dy mo₧nΘ, ₧e se koliznφ bod nachßzφ a₧ n∞kde za novou pozicφ koule. Abychom to zjistili, spoΦφtßme novou pozici a urΦφme vzdßlenost mezi poΦßteΦnφm a koncov²m bodem. Pokud je tato vzdßlenost kratÜφ ne₧ vzdßlenost, o kterou se objekt posune, tak mßme jistotu, ₧e kolize nastane v tomto ΦasovΘm ·seku. Abychom spoΦφtali p°esn² Φas kolize pou₧ijeme nßsledujφcφ jednoduchou rovnici. Dst p°edstavuje vzdßlenost mezi poΦßteΦnφm a koncov²m bodem, Dsc vzdßlenost mezi poΦßteΦnφm a koliznφm bodem a Φasov² krok je definovßn jako T. ╪eÜenφm zφskßme Φas kolize Tc.

Tc = Dsc * T / Dst

V²poΦet se provede samoz°ejm∞ jenom tehdy, kdy₧ mß kolize nastat v tomto ΦasovΘm kroku. Vrßcen² Φas je zlomkem (Φßstφ) celΘho ΦasovΘho kroku. Pokud bude Φasov² krok 1 s a my nalezneme koliznφ bod p°esn∞ uprost°ed vzdßlenosti, Φas kolize se bude rovnat 0,5 s. Je interpretovßn jako: V ΦasovΘm okam₧iku 0,5 sekund po zaΦßtku p°esunu do sebe objekty narazφ. Koliznφ bod se vypoΦte nßsobenφm Φasu Tc aktußlnφ rychlostφ a p°iΦtenφm poΦßteΦnφho bodu.

bod_kolize = start + rychlost * Tc

Tento koliznφ bod je vÜak na objektu s offsetem (pomocnΘm). Abychom nalezli bod nßrazu na reßlnΘm objektu, p°iΦteme k bodu kolize invertovan² normßlov² vektor z bodu kolize, kter² mß velikost polom∞ru koule. Normßlov² vektor zφskßme z funkce pro kolize. VÜimn∞te si, ₧e funkce pro kolizi s vßlcem vracφ bod nßrazu, tak₧e nemusφ b²t znovu poΦφtßn.

Modelovßnφ zalo₧enΘ na fyzice

Reakce na nßraz

OÜet°enφ toho, jak se koule zachovß po nßrazu je stejn∞ d∙le₧itΘ jako samotnΘ nalezenφ koliznφho bodu. Pou₧itΘ algoritmy a funkce popisujφ p°esn² bod nßrazu, normßlov² vektor vychßzejφcφ z objekt∙ v mφst∞ nßrazu a Φasov² ·sek, ve kterΘm kolize nastala.

P°i odrazech nßm pomohou fyzikßlnφ zßkony. Implementujeme pouΦku: "┌hel dopadu se rovnß ·hlu odrazu". Oba ·hly se vztahujφ k normßlovΘmu vektoru, kter² vychßzφ z objektu v koliznφm bod∞. Nßsledujφcφ obrßzek ukazuje odraz polop°φmky od koule.

Odraz od koule

I je sm∞rov² vektor p°ed nßrazem, N je normßlov² vektor v bod∞ kolize a R je sm∞rov² vektor po odrazu, kter² se vypoΦte podle nßsledujφcφ rovnice:

R = 2 * (-I dot N) * N + I

Omezenφ spoΦφvß v tom, ₧e I i N musφ b²t jednotkovΘ vektory. U nßs vÜak dΘlka vektoru reprezentuje rychlost a sm∞r koule, a proto nem∙₧e b²t bez transformace dosazen do rovnice. Pot°ebujeme z n∞j vyjmout rychlost. Nalezneme jeho velikost a vyd∞lφme jφ jednotlivΘ x, y, z slo₧ky. Zφskan² jednotkov² vektor dosadφme do rovnice a vypoΦteme R. Jsme skoro u konce. Vektor nynφ mφ°φ ve sm∞ru odra₧enΘ polop°φmky, ale nemß p∙vodnφ dΘlku. Minule jsme d∞lili, tak₧e te∩ budeme nßsobit.

Nßsledujφcφ v²pis k≤du se pou₧φvß pro v²poΦet odrazu po kolizi koule s rovinou nebo vßlcem. Uveden² algoritmus pracuje i s jin²mi povrchy, nezßle₧φ na jejich tvaru. Pokud nalezneme bod kolize a normßlu, je odraz v₧dy stejn².

rt2 = ArrayVel[BallNr].mag();// Ulo₧φ dΘlku vektoru

ArrayVel[BallNr].unit();// Normalizace vektoru

// V²poΦet odrazu

ArrayVel[BallNr] = TVector::unit((normal * (2 * normal.dot(-ArrayVel[BallNr]))) + ArrayVel[BallNr]);

ArrayVel[BallNr] = ArrayVel[BallNr] * rt2;// Nastavenφ p∙vodnφ dΘlky

Kdy₧ se koule srazφ s jinou

OÜet°enφ vzßjemnΘho nßrazu dvou pohybujφcφch se koulφ je mnohem obtφ₧n∞jÜφ. Musφ b²t vy°eÜeny slo₧itΘ rovnice. Nebudeme nic odvozovat, pouze vysv∞tlφm v²sledek. Situace p°i kolizi dvou koulφ vypadß p°ibli₧n∞ takto:

Srß₧ka dvou koulφ

Vektory U1 a U2 p°edstavujφ rychlost koulφ v Φase nßrazu. St°edy dohromady spojuje osa X_Axis, na kterΘ le₧φ vektory U1x a U2x, co₧ jsou vlastn∞ pr∙m∞ty rychlosti. U1y a U2y jsou projekce rychlosti na osu, kterß je kolmß k X_Axis. K jejich v²poΦtu postaΦφ jednoduch² skalßrnφ souΦin.

Do nßsledujφcφch rovnic dosazujeme jeÜt∞ Φφsla M1 a M2, kterß vyjad°ujφ hmotnost koulφ. Sna₧φme se vypoΦφtat orientaci vektor∙ rychlosti U1 a U2 po odrazu. Budou je vyjad°ovat novΘ vektory V1 a V2. ╚φsla V1x, V1y, V2x, V2y jsou op∞t pr∙m∞ty.

a) najφt X_Axis

X_Axis = (st°ed2 - st°ed1)

Jednotkov² vektor, X_Axis.unit();

b) najφt projekce

U1x = X_Axis * (X_Axis dot U1)

U1y = U1 - U1x

U2x = -X_Axis * (-X_Axis dot U2)

U2y = U2 - U2x

c) najφt novΘ rychlosti

V1x = ((U1x * M1) + (U2x * M2) - (U1x - U2x) * M2) / (M1 + M2)

V2x = ((U1x * M1) + (U2x * M2) - (U2x - U1x) * M1) / (M1 + M2)

V naÜφ aplikaci nastavujeme jednotkovou hmotnost (M1 = M2 = 1), a proto se v²poΦet v²sledn²ch vektor∙ velmi zjednoduÜφ.

d) najφt koneΦnΘ rychlosti

V1y = U1y

V2y = U2y

V1 = V1x + V1y

V2 = V2x + V2y

Odvozenφ rovnic stßlo hodn∞ prßce, ale jakmile se nachßzejφ v tΘto form∞, je jejich pou₧itφ docela snadnΘ. K≤d, kterΘ vykonßvß srß₧ky dvou koulφ vypadß takto:

TVector pb1, pb2, xaxis, U1x, U1y, U2x, U2y, V1x, V1y, V2x, V2y;// Deklarace prom∞nn²ch

double a, b;

pb1 = OldPos[BallColNr1] + ArrayVel[BallColNr1] * BallTime;// Nalezenφ pozice koule 1

pb2 = OldPos[BallColNr2] + ArrayVel[BallColNr2] * BallTime;// Nalezenφ pozice koule 2

xaxis = (pb2 - pb1).unit();// Nalezenφ X_Axis

a = xaxis.dot(ArrayVel[BallColNr1]);// Nalezenφ projekce

U1x = xaxis * a;// Nalezenφ pr∙m∞t∙ vektor∙

U1y = ArrayVel[BallColNr1] - U1x;

xaxis = (pb1 - pb2).unit();

b = xaxis.dot(ArrayVel[BallColNr2]);// To samΘ pro druhou kouli

U2x = xaxis * b;

U2y = ArrayVel[BallColNr2] - U2x;

V1x = (U1x + U2x - (U1x - U2x)) * 0.5;// Nalezenφ nov²ch rychlostφ

V2x = (U1x + U2x - (U2x - U1x)) * 0.5;

V1y = U1y;

V2y = U2y;

for (j = 0; j < NrOfBalls; j++)// Posun vÜech koulφ do Φasu nßrazu

{

ArrayPos[j] = OldPos[j] + ArrayVel[j] * BallTime;

}

ArrayVel[BallColNr1] = V1x + V1y;// Nastavenφ prßv∞ vypoΦφtan²ch vektor∙ koulφm, kterΘ do sebe narazily

ArrayVel[BallColNr2] = V2x + V2y;

Pohyb v gravitaci za pou₧itφ Eulerov²ch rovnic

Pro simulaci realistick²ch pohyb∙ nejsou nßrazy, hledßnφ koliznφch bod∙ a odrazy dostateΦnΘ. Musφ b²t p°idßn jeÜt∞ pohyb podle fyzikßlnφch zßkon∙. Asi nejpou₧φvan∞jÜφ metodou jsou Eulerovy rovnice. VÜechny v²poΦty se vykonßvajφ pro urΦit² Φasov² ·sek. To znamenß, ₧e se celß simulace neposouvß vp°ed plynule, ale po urΦit²ch skocφch. P°edstavte si, ₧e mßte fotoaparßt a ka₧dou vte°inu v²slednou scΘnu vyfotφte. B∞hem tΘto vte°iny se provedou vÜechny pohyby, testy kolizφ a odrazy. V²sledn² obrßzek se zobrazφ na monitoru a z∙stane tam a₧ do dalÜφ vte°iny. Op∞t stejnΘ v²poΦty a dalÜφ zobrazenφ. Takto pracujφ vÜechny poΦφtaΦovΘ animace, ale mnohem rychleji. Oko, stejn∞ jako u filmu, vidφ plynul² pohyb. V zßvislosti na Eulerov²ch rovnicφch se rychlost a pozice v ka₧dΘm ΦasovΘm kroku zm∞nφ takto:

novß_rychlost = starß_rychlost + zrychlenφ * Φasov² ·sek

novß_pozice = starß_pozice + novß_rychlost * Φasov² ·sek

Nynφ se objekty pohybujφ a testujφ na kolize s pou₧itφm novΘ rychlosti. Zrychlenφ objektu je zφskßno vyd∞lenφm sφly, kterß na n∞j p∙sobφ, jeho hmotnostφ.

zrychlenφ = sφla / hmotnost

V tomto demu je gravitace jedinß sφla, kterß p∙sobφ na objekt. M∙₧e b²t reprezentovßna vektorem, kter² udßvß gravitaΦnφ zrychlenφ. U nßs se bude tento vektor rovnat (0; -0,5; 0). To znamenß, ₧e na zaΦßtku ka₧dΘho ΦasovΘho ·seku spoΦφtßme novou rychlost koule a s testovßnφm kolizφ ji posuneme. Pokud b∞hem ΦasovΘho ·seku narazφ (nap°. po 0,5 s), posuneme ji na pozici kolize, vypoΦteme odraz (nov² vektor rychlosti) a p°esuneme ji o zb²vajφcφ Φas (0,5 s). V n∞m op∞t testujeme kolize atd. Opakujeme tak dlouho, dokud zb²vß n∞jak² Φas.

Pokud je p°φtomno vφce pohybujφcφch se objekt∙, musφ b²t nejprve testovßn ka₧d² z nich na nßrazy do statick²ch objekt∙. Ulo₧φ se Φasov∞ nejbli₧Üφ z nich. Potom se provedou testy nßraz∙ mezi pohybujφcφmi se objekty - ka₧d² s ka₧d²m. Vrßcen² Φas je porovnßn s Φasem u test∙ se statick²mi objekty a v ·vahu je brßn nejbli₧Üφ nßraz. Celß simulace se posune do tohoto Φasu. VypoΦte se odraz objektu a op∞t se provedou detekce nßraz∙ do statick²ch objekt∙ atd. atd. atd. - dokud zb²vß n∞jak² Φas. P°ekreslφ se scΘna a vÜe se opakuje nanovo.

Specißlnφ efekty

Exploze

Kdykoli, kdy₧ se objekty srazφ, nastane exploze, kterß se zobrazφ na sou°adnicφch pr∙seΦφku. Velmi jednoduchou cestou je alfablending dvou polygon∙, kterΘ jsou navzßjem kolmΘ a jejich st°ed je na sou°adnicφch koliznφho bodu. Oba polygony se postupn∞ zv∞tÜujφ a zpr∙hled≥ujφ. Alfa hodnota se zmenÜuje z poΦßteΦnφ jedniΦky a₧ na nulu. Dφky Z bufferu m∙₧e spousta alfablendovan²ch polygon∙ zp∙sobovat problΘmy - navzßjem se p°ekr²vajφ, a proto si p∙jΦφme techniku pou₧φvanou p°i renderingu Φßstic. Abychom vÜe d∞lali sprßvn∞, musφme polygony °adit od zadnφch po p°ednφ podle vzdßlenosti od pozorovatele. TakΘ vypneme zßpis do Depth bufferu (ne Φtenφ). VÜimn∞te si, ₧e omezujeme poΦet explozφ na maximßln∞ dvacet na jeden snφmek. Nastane-li jich najednou vφce, pole se zaplnφ a dalÜφ se nebudou brßt v ·vahu. Nßsleduje k≤d, kter² aktualizuje a renderuje exploze.

glEnable(GL_BLEND);// Blending

glDepthMask(GL_FALSE);// Vypne zßpis do depth bufferu

glBindTexture(GL_TEXTURE_2D, texture[1]);// Textura exploze

for(i = 0; i < 20; i++)// Prochßzφ v²buchy

{

if(ExplosionArray[i]._Alpha >= 0)// Je exploze vid∞t?

{

glPushMatrix();// Zßloha matice

ExplosionArray[i]._Alpha -= 0.01f;// Aktualizace alfa hodnoty

ExplosionArray[i]._Scale += 0.03f;// Aktualizace m∞°φtka

glColor4f(1, 1, 0, ExplosionArray[i]._Alpha);// Älutß barva s pr∙hlednostφ

glScalef(ExplosionArray[i]._Scale, ExplosionArray[i]._Scale, ExplosionArray[i]._Scale);// Zm∞na m∞°φtka

glTranslatef((float)ExplosionArray[i]._Position.X() / ExplosionArray[i]._Scale, (float)ExplosionArray[i]._Position.Y() / explosionArray[i]._Scale, (float)ExplosionArray[i]._Position.Z() / ExplosionArray[i]._Scale);// P°esun na pozici koliznφho bodu, m∞°φtko je offsetem

glCallList(dlist);// Zavolß display list

glPopMatrix();// Obnova p∙vodnφ matice

}

}

glDepthMask(GL_TRUE);// Obnova p∙vodnφch parametr∙ OpenGL

glDisable(GL_BLEND);

glDisable(GL_TEXTURE_2D);

Zvuky

Pro p°ehrßvßnφ zvuk∙ se pou₧φvß funkce PlaySound() z multimedißlnφ knihovny Windows - rychlß cesta, jak bez problΘm∙ p°ehrßt .wav zvuk.

Vysv∞tlenφ k≤du

Gratuluji... pokud stßle Φtete, ·sp∞Ün∞ jste se prokousali dlouhou a nßroΦnou teoretickou sekcφ. P°edtφm, ne₧ si zaΦnete hrßt s demem, m∞l by b²t jeÜt∞ vysv∞tlen zdrojov² k≤d. Ze vÜeho nejd°φve se ale p∙jdeme podφvat na globßlnφ prom∞nnΘ.

Vektory dir a pos reprezentujφ pozici a sm∞r kamery, kterou v programu pohybujeme funkcφ gluLookAt(). Pokud scΘna nenφ vykreslovßna v m≤du "sledovßnφ koule", otßΦφ se kolem osy y.

TVector dir;// Sm∞r kamery

TVector pos(0, -50, 1000);// Pozice kamery

float camera_rotation = 0;// Rotace scΘny na ose y

Gravitace, kterß p∙sobφ na koule.

TVector accel(0, -0.05, 0);// GravitaΦnφ zrychlenφ aplikovanΘ na koule

Pole, kterß uklßdajφ novou a starou pozici vÜech koulφ a jejich sm∞r. PoΦet koulφ je natvrdo nastaven na deset.

TVector ArrayVel[10];// Rychlost koulφ

TVector ArrayPos[10];// Pozice koulφ

TVector OldPos[10];// StarΘ pozice koulφ

╚asov² ·sek pro simulaci.

double Time = 0.6;// ╚asov² krok simulace

Pokud je tato prom∞nnß v jedniΦce, zm∞nφ se m≤d kamery tak, aby sledovala pohyby koule. Pro jejφ umφst∞nφ a nasm∞rovßnφ se pou₧ije pozice a sm∞r koule s indexem 1, kterß tedy bude v₧dy v zßb∞ru.

int hook_toball1 = 0;// Sledovat kamerou kouli?

Nßsledujφcφ struktury se popisujφ samy sv²m jmΘnem. Budou uklßdat data o rovinßch, vßlcφch a explozφch.

struct Plane// Struktura roviny

{

TVector _Position;

TVector _Normal;

};

struct Cylinder// Struktura vßlce

{

TVector _Position;

TVector _Axis;

double _Radius;

};

struct Explosion// Struktura exploze

{

TVector _Position;

float _Alpha;

float _Scale;

};

Objekty struktur.

Plane pl1, pl2, pl3, pl4, pl5;// P∞t rovin mφstnosti (bez stropu)

Cylinder cyl1, cyl2, cyl3;// T°i vßlce

Explosion ExplosionArray[20];// Dvacet explozφ

Textury, display list, quadratic.

GLuint texture[4];// ╚ty°i textury

GLuint dlist;// Display list v²buchu

GLUquadricObj *cylinder_obj;// Quadratic pro kreslenφ koulφ a vßlc∙

Funkce pro kolize koulφ se statick²mi objekty a mezi koulemi navzßjem.

int TestIntersionPlane(const Plane& plane, const TVector& position, const TVector& direction, double& lamda, TVector& pNormal);

int TestIntersionCylinder(const Cylinder& cylinder, const TVector& position, const TVector& direction, double& lamda, TVector& pNormal, TVector& newposition);

int FindBallCol(TVector& point, double& TimePoint, double Time2, int& BallNr1, int& BallNr2);

Loading textur, inicializace prom∞nn²ch, logika simulace, renderovßnφ scΘny a inicializace OpenGL.

void LoadGLTextures();

void InitVars();

void idle();

int DrawGLScene(GLvoid);

int InitGL(GLvoid)

Pro informace o geometrick²ch t°φdßch vektoru, polop°φmky a matice nahlΘdn∞te do zdrojov²ch k≤d∙. Jsou velmi u₧iteΦnΘ a mohou b²t bez problΘm∙ vyu₧ity ve vaÜich vlastnφch programech.

Nejd∙le₧it∞jÜφ kroky simulace nejprve popφÜi pseudok≤dem.

while (Φasov² ·sek != 0)

{

for (ka₧dß koule)

{

V²poΦet nejbli₧Üφ kolize s rovinami;

V²poΦet nejbli₧Üφ kolize s vßlci;

Ulo₧it/nahradit "zßznam" o kolizi, pokud je to do te∩ nejbli₧Üφ kolize v Φase;

}

Testy kolizφ mezi pohybujφcφmi se koulemi;

Ulo₧it/nahradit "zßznam" o kolizi, pokud je to do te∩ nejbli₧Üφ kolize v Φase;

if (nastala kolize?)

{

P°esun vÜech koulφ do Φasu nejbli₧Üφ kolize;

(U₧ mßme vypoΦten bod, normßlu a Φas kolize.)

V²poΦet odrazu;

Φasov² ·sek -= Φas kolize;

}

else

{

P°esun vÜech koulφ na konec ΦasovΘho ·seku;

}

}

Zdrojov² k≤d zalo₧en² na pseudok≤du je na prvnφ pohled mnohem vφce nßroΦn² na Φtenφ a hlavn∞ pochopenφ, nicmΘn∞ v zßkladu je jeho p°esnou implementacφ.

void idle()// SimulaΦnφ logika - kolize

{

// Deklarace prom∞nn²ch

double rt, rt2, rt4, lamda = 10000;

TVector norm, uveloc;

TVector normal, point, time;

double RestTime, BallTime;

TVector Pos2;

int BallNr = 0, dummy = 0, BallColNr1, BallColNr2;

TVector Nc;

if (!hook_toball1)// Pokud kamera nesleduje kouli

{

camera_rotation += 0.1f;// PootoΦenφ scΘny

if (camera_rotation > 360)// OÜet°enφ p°eteΦenφ

{

camera_rotation = 0;

}

}

RestTime = Time;

lamda = 1000;

// V²poΦet rychlostφ vÜech koulφ pro nßsledujφcφ Φasov² ·sek (Eulerovy rovnice)

for (int j = 0; j < NrOfBalls; j++)

{

ArrayVel[j] += accel * RestTime;

}

while (RestTime > ZERO)// Dokud neskonΦil Φasov² ·sek

{

lamda = 10000;// Inicializace na velmi vysokou hodnotu

// Kolize vÜech koulφ s rovinami a vßlci

for (int i = 0; i < NrOfBalls; i++)// VÜechny koule

{

// V²poΦet novΘ pozice a vzdßlenosti

OldPos[i] = ArrayPos[i];

TVector::unit(ArrayVel[i], uveloc);

ArrayPos[i] = ArrayPos[i] + ArrayVel[i] * RestTime;

rt2 = OldPos[i].dist(ArrayPos[i]);

// Kolize koule s rovinou

if (TestIntersionPlane(pl1, OldPos[i], uveloc, rt, norm))

{

// ╚as nßrazu

rt4 = rt * RestTime / rt2;

// Pokud je menÜφ ne₧ n∞kter² z d°φve nalezen²ch nahradit ho

if (rt4 <= lamda)

{

if (rt4 <= RestTime + ZERO)

{

if (!((rt <= ZERO) && (uveloc.dot(norm) > ZERO)))

{

normal = norm;

point = OldPos[i] + uveloc * rt;

lamda = rt4;

BallNr = i;

}

}

}

}

if (TestIntersionPlane(pl2, OldPos[i], uveloc, rt, norm))

{

// To samΘ jako minule, ale s jinou rovinou

}

if (TestIntersionPlane(pl3, OldPos[i], uveloc, rt, norm))

{

// To samΘ jako minule, ale s jinou rovinou

}

if (TestIntersionPlane(pl4, OldPos[i], uveloc, rt, norm))

{

// To samΘ jako minule, ale s jinou rovinou

}

if (TestIntersionPlane(pl5, OldPos[i], uveloc, rt, norm))

{

// To samΘ jako minule, ale s jinou rovinou

}

// Kolize koule s vßlcem

if (TestIntersionCylinder(cyl1, OldPos[i], uveloc, rt, norm, Nc))

{

rt4 = rt * RestTime / rt2;

if (rt4 <= lamda)

{

if (rt4 <= RestTime + ZERO)

{

if (!((rt <= ZERO) && (uveloc.dot(norm) > ZERO)))

{

normal = norm;

point = Nc;

lamda = rt4;

BallNr = i;

}

}

}

}

if (TestIntersionCylinder(cyl2, OldPos[i], uveloc, rt, norm, Nc))

{

// To samΘ jako minule, ale s jin²m vßlcem

}

if (TestIntersionCylinder(cyl3, OldPos[i], uveloc, rt, norm, Nc))

{

// To samΘ jako minule, ale s jin²m vßlcem

}

}

// Kolize mezi koulemi

if (FindBallCol(Pos2, BallTime, RestTime, BallColNr1, BallColNr2))

{

if (sounds)// Jsou zapnutΘ zvuky?

{

PlaySound("Data/Explode.wav", NULL, SND_FILENAME | SND_ASYNC);

}

if ((lamda == 10000) || (lamda > BallTime))

{

RestTime = RestTime - BallTime;

TVector pb1, pb2, xaxis, U1x, U1y, U2x, U2y, V1x, V1y, V2x, V2y;// Deklarace prom∞nn²ch

double a, b;

pb1 = OldPos[BallColNr1] + ArrayVel[BallColNr1] * BallTime;// Nalezenφ pozice koule 1

pb2 = OldPos[BallColNr2] + ArrayVel[BallColNr2] * BallTime;// Nalezenφ pozice koule 2

xaxis = (pb2 - pb1).unit();// Nalezenφ X_Axis

a = xaxis.dot(ArrayVel[BallColNr1]);// Nalezenφ projekce

U1x = xaxis * a;// Nalezenφ pr∙m∞t∙ vektor∙

U1y = ArrayVel[BallColNr1] - U1x;

xaxis = (pb1 - pb2).unit();

b = xaxis.dot(ArrayVel[BallColNr2]);// To samΘ pro druhou kouli

U2x = xaxis * b;

U2y = ArrayVel[BallColNr2] - U2x;

V1x = (U1x + U2x - (U1x - U2x)) * 0.5;// Nalezenφ nov²ch rychlostφ

V2x = (U1x + U2x - (U2x - U1x)) * 0.5;

V1y = U1y;

V2y = U2y;

for (j = 0; j < NrOfBalls; j++)// Aktualizace pozic vÜech koulφ

{

ArrayPos[j] = OldPos[j] + ArrayVel[j] * BallTime;

}

ArrayVel[BallColNr1] = V1x + V1y;// Nastavenφ prßv∞ vypoΦφtan²ch vektor∙ koulφm, kterΘ do sebe narazily

ArrayVel[BallColNr2] = V2x + V2y;

// Aktualizace pole explozφ

for(j = 0; j < 20; j++)// VÜechny exploze

{

if (ExplosionArray[j]._Alpha <= 0)// Hledß volnΘ mφsto

{

ExplosionArray[j]._Alpha = 1;// Nepr∙hlednß

ExplosionArray[j]._Position = ArrayPos[BallColNr1];// Pozice

ExplosionArray[j]._Scale = 1;// M∞°φtko

break;// UkonΦit prohledßvßnφ

}

}

continue;// Opakovat cyklus

}

}

// Konec test∙ kolizφ

// Pokud se proÜel cel² Φasov² ·sek a byly vypoΦteny reakce koulφ, kterΘ narazily

if (lamda != 10000)

{

RestTime -= lamda;// OdeΦtenφ Φasu kolize od ΦasovΘho ·seku

for (j = 0; j < NrOfBalls; j++)

{

ArrayPos[j] = OldPos[j] + ArrayVel[j] * lamda;

}

rt2 = ArrayVel[BallNr].mag();

ArrayVel[BallNr].unit();

ArrayVel[BallNr] = TVector::unit((normal * (2 * normal.dot(-ArrayVel[BallNr]))) + ArrayVel[BallNr]);

ArrayVel[BallNr] = ArrayVel[BallNr] * rt2;

// Aktualizace pole explozφ

for(j = 0; j < 20; j++)// VÜechny exploze

{

if (ExplosionArray[j]._Alpha <= 0)// Hledß volnΘ mφsto

{

ExplosionArray[j]._Alpha = 1;// Nepr∙hlednß

ExplosionArray[j]._Position = ArrayPos[BallColNr1];// Pozice

ExplosionArray[j]._Scale = 1;// M∞°φtko

break;// UkonΦit prohledßvßnφ

}

}

}

else

{

RestTime = 0;// UkonΦenφ hlavnφho cyklu a vlastn∞ i funkce

}

}

}

Jak jsem u₧ napsal na zaΦßtku, p°edm∞t kolizφ je velmi t∞₧k² a rozsßhl², aby se dal popsat jen v jednom tutorißlu, p°esto jste se nauΦili spoustu nov²ch v∞cφ. M∙₧ete zaΦφt vytvß°et vlastnφ p∙sobivß dema. Nynφ, kdy₧ chßpete zßklady, budete lΘpe rozum∞t i cizφm zdrojov²m k≤d∙m, kterΘ vßs zase posunou o kousek dßl. P°eji hodn∞ Üt∞stφ.

napsal: Dimitrios Christopoulos
p°elo₧il: Michal Turek - Woq

Informace o autorovi

V souΦasnΘ dob∞ pracuje jako softwarov² in₧en²r virtußlnφ reality HelΘnskΘho sv∞ta v AtΘnßch/╪ecko (www.fhw.gr). AΦkoli se narodil v N∞mecku, studoval °eckou univerzitu Patras na bakalß°e p°φrodnφch v∞d v poΦφtaΦovΘm in₧en²rstvφ a informatice. Je takΘ dr₧itelem MSc degree (titul Magistra p°φrodnφch v∞d) z univerzity Hull (Anglie) v poΦφtaΦovΘ grafice a virtußlnφm prost°edφ.

Prvnφ kr∙Φky s programovßnφm podnikl v jazyce Basic na Commodoru 64. Po zaΦßtku studia se p°eorientoval na C/C++/Assembler na platform∞ PC. B∞hem n∞kolika minul²ch let si jako grafickΘ API zvolil OpenGL.

ZdrojovΘ k≤dy

Lekce 30

<<< Lekce 29 | Lekce 31 >>>