82

Diplomski rad IZRADA INTERAKTIVNE ANIMACIJE …planinic/diplomski/goran_jambresic.pdfZahaljujemv se svom mentoru, prof. dr. sc. Mirku Planini¢u, ²to mi je omogu¢io i pomogao da

  • Upload
    others

  • View
    3

  • Download
    1

Embed Size (px)

Citation preview

SVEU�ILI�TE U ZAGREBUPRIRODOSLOVNO-MATEMATI�KI FAKULTET

FIZI�KI ODSJEK

Goran Jambre²i¢

Diplomski rad

IZRADA INTERAKTIVNE ANIMACIJE ZA

SIMULIRANJE SKOKA S PADOBRANOM

Zagreb, 2011.

SVEU�ILI�TE U ZAGREBUPRIRODOSLOVNO-MATEMATI�KI FAKULTET

FIZI�KI ODSJEK

SMJER: PROFESOR FIZIKE I INFORMATIKE

Goran Jambre²i¢

Diplomski rad

IZRADA INTERAKTIVNE ANIMACIJE ZA

SIMULIRANJE SKOKA S PADOBRANOM

Voditelj diplomskog rada: prof. dr. sc. Mirko Planini¢

Ocjena diplomskog rada:

Povjerenstvo: 1.

2.

3.

Datum polaganja:

Zagreb, 2011.

Zahvaljujem se svom mentoru, prof. dr. sc. Mirku Planini¢u, ²to mi je

omogu¢io i pomogao da ostvarim svoju viziju £ime sam stekao jedno

neprocjenjivo iskustvo.

Posebno se zahvaljujem svojoj obitelji koja mi je bila neizmjerna i

bezrezervna podr²ka na svakom koraku.

Sadrºaj

Uvod 1

1 Fizikalni modeli 2

1.1 Analiti£ko razmatranje modela . . . . . . . . . . . . . . . . . . 21.1.1 Model sa otporom proporcionalnim brzini . . . . . . . 21.1.2 Model sa otporom proporcionalnim kvadratu brzine . . 6

1.2 Implementacija i modi�kacija modela . . . . . . . . . . . . . . 91.2.1 Numeri£ka implementacija modela . . . . . . . . . . . . 91.2.2 Realisti£an model skoka s padobranom . . . . . . . . . 10

2 Izrada simulacije 16

2.1 Ciljevi izrade . . . . . . . . . . . . . . . . . . . . . . . . . . . 162.2 Alati za izradu . . . . . . . . . . . . . . . . . . . . . . . . . . 172.3 Princip funkcioniranja Panda3D enginea . . . . . . . . . . . . 172.4 Kreiranje okoline . . . . . . . . . . . . . . . . . . . . . . . . . 202.5 Kreiranje doga�aja . . . . . . . . . . . . . . . . . . . . . . . . 222.6 Kreiranje korisni£kog su£elja . . . . . . . . . . . . . . . . . . . 232.7 Kreiranje i pakiranje aplikacije . . . . . . . . . . . . . . . . . . 24

Mogu¢nosti uporabe u nastavi �zike i informatike 26

Zaklju£ak 27

Izvorni kod 28

Literatura 78

iv

Uvod

Tijekom ²kolovanja u£enici se £esto susre¢u s razli£itim konceptima klasi£nemehanike pri £emu se ti koncepti razmatraju na pojednostavljen na£in £ime seponekad gubi osje¢aj za �zikalnu stvarnost neke pojave. Jedan od primjeratoga je razmatranje slobodnog pada koje se, zbog matemati£ke sloºenosti,vr²i bez dubljeg uzimanja utjecaja otpora zraka u obzir.

Cilj ovog diplomskog rada je bila izrada simulacije koja ¢e na �zikalnoprihvatljiv na£in prikazati slobodan pad sa svim aspektima kako bi u£enicimogli ste¢i osje¢aj za �zikalnu stvarnost ove pojave. Za demonstriranje slo-bodnog pada odabrano je skakanje padobranom pod pretpostavkom da ¢e tou£enicima biti dovoljno zoran i zanimljiv primjer koji iako im nije nepoznatnije ba² ni dio njihove svakodnevice.

Prilikom kori²tenja ra£unala u nastavi �zike puno problema se demons-trira pomo¢u Flash animacija ili Java appleta. Ovaj rad se razlikuje po tome²to je za izradu kori²ten 3D engine te je svojevrstan tehnolo²ki demonstratormogu¢nosti koje takvi sustavi nude.

1

Poglavlje 1

Fizikalni modeli

1.1 Analiti£ko razmatranje modela

Da bi uspje²no izradili simulaciju trebamo imati model koji na vjerodostojanna£in prikazuje �zikalnu pojavu. Razmotriti ¢emo dva modela koja se £estonalaze u udºbenicima [1]. Jedan model pretpostavlja da je iznos sile otpora�uida proporcionalan brzini tijela koje se giba u �uidu dok drugi modelpretpostavlja da je taj iznos proporcionalan kvadratu brzine. U oba slu£ajapolazimo od raspisa modela preko 2. Newtonovog zakona kako bi do²li dojednadºbi gibanja koje su nam potrebne te do izraza za maksimalnu brzinukoju tijelo moºe posti¢i tj. terminalnu brzinu vt.

1.1.1 Model sa otporom proporcionalnim brzini

Brzina

Razmatramo slu£aj u kojem su zadani sljede¢i po£etni uvjeti za vrijeme ibrzinu: t = 0 → t, v = 0 → v. Polazimo od jednadºbe:

F = mg − kv

pri £emu je: F ukupna sila koja djeluje na tijelo, m masa tijela koje pada, gubrzanje sile teºe, k koe�cijent otpora, a v brzina tijela.Iz po£etne jednadºbe mogu¢e je dobiti i terminalnu brzinu, vt. Za F = 0 →vt =

mgk

Raspisujemo po£etnu jednadºbu:

ma = mg − kv

mδv

δt= mg − kv

2

δv

δt= g − kv

mδv

g − kvm

= δt

δvkm

(mgk

− v) = δt

m

k

∫ 1

vt − vdv =

∫dt

−m

kln (vt − v) = t+ C

C = −m

kln (vt − v)− t

Za v = 0, t = 0 → C = −mkln vt

−m

kln (vt − v) = t− m

kln vt

−m

kln (vt − v) +

m

kln vt = t

ln vt − ln (vt − v) =kt

m

ln(

vtvt − v

)=

kt

mvt

vt − v= e

ktm

vt − v = vte− kt

m

v = vt(1− e−

ktm

)

pri £emu je: v brzina tijela, vt terminalna brzina tijela, k koe�cijent otpora, tvrijeme, a m masa tijela. Dobiveni izraz se koristi prilikom izvo�enja izrazaza akceleraciju i poloºaj.

Akceleracija

a =δ(vt(1− e−

ktm

))δt

a =vtke

− ktm

m

3

a = ge−ktm

pri £emu je: a akceleracija tijela, g ubrzanje sile teºe, k koe�cijent otpora, tvrijeme, a m masa tijela.

Poloºaj

y =∫vt(1− e−

ktm

)dt

y =mvte

− ktm

k+ vtt+ C

C = y − mvte− kt

m

k− vtt

Za y = 0, t = 0 → C = −mvtk

y =mvte

− ktm

k+ vtt−

mvtk

y =mvt

(e−

ktm − 1

)k

+ vtt

pri £emu je: y poloºaj tijela, m masa tijela, vt terminalna brzina tijela, kkoe�cijent otpora, a t vrijeme.

Dobiveni izrazi za brzinu, akceleraciju i poloºaj su valjani samo ako vrijedezadani po£etni uvjeti. Budu¢i da skok padobranom u principu ima razli£iteuvjete prije, tijekom i nakon otvaranja padobrana (koe�cijent otpora ne os-taje isti) potrebno je modi�cirati dobivene jednadºbe.

Brzina

Ponovno izvodimo formulu za brzinu, no sada uzimamo druk£ije po£etneuvjete. Izvod je identi£an prethodnome sve do raspisa konstante.Razmatramo slu£aj za t = tp → t, v = vp → v pri £emu je tp po£etno vrijeme,a vp po£etna brzina.

−m

kln (vt − v) = t+ C

C = −m

kln (vt − v)− t

4

Za v = vp, t = tp → C = −mkln (vt − vp)− tp

−m

kln (vt − v) = t− m

kln (vt − vp)− tp

−m

kln (vt − v) +

m

kln (vt − vp) = t− tp

ln (vt − vp)− ln (vt − v) =k (t− tp)

m

ln

((vt − vp)

vt − v

)=

k (t− tp)

m

vt − vpvt − v

= ek(t−tp)

m

vt − v = (vt − vp) e− k(t−tp)

m

v = vt − (vt − vp) e− k(t−tp)

m

pri £emu je: v brzina tijela, vt terminalna brzina tijela, vp po£etna brzinatijela, k koe�cijent otpora, t vrijeme, tp po£etno vrijeme, a m masa tijela.Dobiveni izraz se koristi prilikom izvo�enja izraza za akceleraciju i poloºaj.

Akceleracija

a =δ(vt − (vt − vp) e

− k(t−tp)m

)δt

a =(vt − vp) ke

− k(t−tp)m

m

pri £emu je: a akceleracija tijela, vt terminalna brzina tijela, vp po£etna br-zina tijela, k koe�cijent otpora, t vrijeme, tp po£etno vrijeme, ammasa tijela.

Poloºaj

y =∫ (

vt − (vt − vp) e− k(t−tp)

m

)dt

y = −(vp − vt) e− k(tp−t)

m

k+ vtt+ C

C = y +(vp − vt) e

− k(tp−t)m

k− vtt

5

Za y = yp, t = tp → C = yp +(vp−vt)e

−k(tp−tp)

m

k− vttp

C = yp +(vp − vt) e

− k(tp−tp)m

k− vttp

C = yp +(vp − vt)m

k− vttp

y = −(vp − vt)me−k(tp−t)

m

k+ vtt+ yp +

(vp − vt)m

k− vttp

y =(vp − vt)m

(1− e−

k(tp−t)m

)k

+ vt (t− tp) + yp

pri £emu je: y poloºaj tijela, vp po£etna brzina tijela, vt terminalna brzinatijela, m masa tijela, k koe�cijent otpora, tp po£etno vrijeme, t vrijeme, a yppo£etni poloºaj tijela.

Sada imamo sve potrebne izraze u zadovoljavaju¢em obliku no ne¢emo ihnaºalost koristiti. Ovaj model daje �zikalno vjerodostojne rezultate samo zamale brzine (≈ 5 m/s) ²to je odgovaraju¢e za dio skoka u kojem je padobranotvoren no ne i za dio skoka sa zatvorenim padobranom u kojem su brzineznatno ve¢e (≈ 45 m/s). Vi²e detalja o ovome biti ¢e dano kasnije.

1.1.2 Model sa otporom proporcionalnim kvadratu brzine

Brzina

Razmatramo slu£aj u kojem su zadani sljede¢i po£etni uvjeti za vrijeme ibrzinu: t = 0 → t, v = 0 → v. Polazimo od jednadºbe:

F = mg − kv2

pri £emu je: F ukupna sila koja djeluje na tijelo, m masa tijela koje pada, gubrzanje sile teºe, k koe�cijent otpora, a v brzina tijela.Iz po£etne jednadºbe mogu¢e je dobiti i terminalnu brzinu, vt. Za F = 0 →vt =

√mgk

Raspisujemo po£etnu jednadºbu:

ma = mg − kv2

mδv

δt= mg − kv2

6

δv

δt= g − kv2

mδv

g − kv2

m

= δt

δvkm

(mgk

− v2) = δt

m

k

∫ 1

v2t − v2dv =

∫dt

m

kvtarctanh

(v

vt

)= t+ C

C =m

kvtarctanh

(v

vt

)− t

Za v = 0, t = 0 → C = 0

m

kvtarctanh

(v

vt

)= t

arctanh(v

vt

)=

tkvtm

v

vt= tanh

(tkvtm

)

v = vt tanh

(tkvtm

)

pri £emu je: v brzina tijela, vt terminalna brzina tijela, t vrijeme, k koe�cijentotpora, a m masa tijela. Dobiveni izraz se koristi prilikom izvo�enja izrazaza akceleraciju i poloºaj.

Akceleracija

a =δ(vt tanh

(tkvtm

))δt

a =vtk

mvt sech

(tkvtm

)2

a =v2t k

m

1

cosh(tkvtm

)27

a =g

cosh(tkvtm

)2pri £emu je: a akceleracija tijela, g ubrzanje sile teºe, t vrijeme, k koe�cijentotpora, a m masa tijela.

Poloºaj

y =∫vt tanh

(tkvtm

)dt

y = vt

∫tanh

(tkvtm

)dt

y = vtm

vtkln

(cosh

(tkvtm

))+ C

C = y − m

kln

(cosh

(tkvtm

))

Za y = 0, t = 0 → C = 0

y =m

kln

(cosh

(tkvtm

))

pri £emu je: y poloºaj tijela, m masa tijela, k koe�cijent otpora, t vrijeme, avt terminalna brzina tijela.

Kao i u prethodnom modelu dobiveni izrazi za brzinu, akceleraciju i po-loºaj su valjani samo ako vrijede zadani po£etni uvjeti. Ponovno je nuºnomodi�cirati dobivene jednadºbe.

Brzina

Uzimamo nove po£etne uvjete, izvod je identi£an po£etnom sve do raspisakonstante. Razmatramo slu£aj za t = tp → t, v = vp → v pri £emu je tppo£etno vrijeme, a vp po£etna brzina.

m

kvtarctanh

(v

vt

)= t+ C

C =m

kvtarctanh

(v

vt

)− t

8

Za v = vp, t = tp → C = mkvt

arctanh(vpvt

)− tp

m

kvtarctanh

(v

vt

)= t+

m

kvtarctanh

(vpvt

)− tp

arctanh(v

vt

)=

kvtt

m− kvttp

m+ arctanh

(vpvt

)arctanh

(v

vt

)=

kvt (t− tp)

m+ arctanh

(vpvt

)v

vt= tanh

(kvt (t− tp)

m+ arctanh

(vpvt

))

v = vt tanh

(kvt (t− tp)

m+ arctanh

(vpvt

))

pri £emu je: v brzina tijela, vt terminalna brzina tijela, k koe�cijent otpora,t vrijeme, tp po£etno vrijeme, m masa tijela, a vp po£etna brzina tijela.

Dobiveni izraz za brzinu ne¢emo koristiti u simulaciji niti ¢emo pomo¢unjega izvoditi daljnje izraze za akceleraciju i poloºaj. Razlog tome je ²to funk-cija arctanh daje realna rje²enja samo za vrijednosti u intervalu < −1, 1 >,a argumenti koje ta funkcija prima prilikom izmjene stanja u skoku tj. otva-ranja padobrana nisu u tom intervalu. Iako je ovaj model op¢enito �zikalnovjerodostojan i prikladniji od modela koji pretpostavlja da je otpor proporci-onalan brzini ne moºemo ga izravno implementirati u simulaciju ve¢ moramoprona¢i alternativan na£in.

1.2 Implementacija i modi�kacija modela

Razmatranjem modela vidjeli smo da model kojeg ºelimo koristiti nije mo-gu¢e u potpunosti analiti£ki izvesti. Budu¢i da se simulacija vr²i na ra£unalumogu¢e je napraviti numeri£ku implementaciju ºeljenog modela pri £emu sekoristi varijanta Eulerove metode [2]. Tako�er ¢emo napraviti ekstenzivnumodi�kaciju numeri£ke implementacije kako bi svi �zikalni aspekti skoka spadobranom bili ²to vjerodostojniji.

1.2.1 Numeri£ka implementacija modela

Osnovna ideja na kojoj se zasniva numeri£ki prora£un je sljede¢a. Znamoda se akceleracija mijenja ovisno o brzini. Ako uzmemo dovoljno malenvremenski interval ∆t moºemo smatrati akceleraciju prakti£ki konstantnom.

9

Znamo li poloºaj i brzinu u nekom trenutku t moºemo ih prona¢i i u nekomkasnijem trenutku t+∆t koriste¢i izraze za konstantnu akceleraciju.

Tijekom vremenskog intervala ∆t prosje£na akceleracija je a = ∆v/∆t tese brzina mijenja za iznos ∆v = a∆t. Zna£i, moºemo zapisati:

v +∆v = v + a∆t (1.1)

Dok se ovo zbiva tijelo se giba te se mijenja i poloºaj. Prosje£na brzinatijekom intervala ∆t je prosje£an iznos brzine na po£etku intervala, v, tebrzine na kraju intervala, v + ∆v, ili v + ∆v/2. Tijekom ∆t poloºaj sepromijeni za iznos:

∆y = (v +∆v/2)∆t = v∆t+1

2a(∆t)2

Poloºaj tijela na kraju intervala je:

y +∆y = y + v∆t+1

2a(∆t)2 (1.2)

Koristimo sljede¢i algoritam:

Korak 1: postave se po£etne vrijednosti za v, y,∆t

Korak 2: ra£una se akceleracija: a = −g − (k/m)v2

Korak 3: ispi²u se vrijednosti za a, v, y

Korak 4: ra£una se brzina pomo¢u izraza (1.1)

Korak 5: ra£una se poloºaj pomo¢u izraza (1.2)

Korak 6: vrijeme se pove¢ava za ∆t: t = t+∆t

Korak 7: prekid

Ponavljamo korake 2-6 sve dok nam je potrebno. Na ovaj na£in moºemo us-pje²no implementirati model sa otporom proporcionalnim kvadratu brzine.

1.2.2 Realisti£an model skoka s padobranom

Numeri£ka implementacija koju smo dobili pruºa �eksibilnost koja ¢e nambiti od velike koristi. Vrijeme je da malo detaljnije promotrimo speci�£neaspekte skoka s padobranom, pri £emu ¢emo se obilno koristiti razvijenim

10

modelom, podacima i formulama iz literature [3], kako bi napravili potrebnemodi�kacije numeri£ke implementacije.

Realisti£an model skoka s padobranom temelji se na osnovnim principimadinamike �uida. Gibanje �uida opisuje se Navier-Stokesovom jednadºbom:

ρ

(δv

δt+ v · ∇v

)= −∇p+ µ∇2v + f

pri £emu je: ρ gusto¢a �uida, v brzina �uida, p tlak, µ viskoznost �uida,a f vanjska sila koja djeluje na �uid. Navier-Stokesova jednadºba sadrºi iviskozne i inercijalne sile. Relativnu vaºnost tih sila u toku mogu¢e je opisatibezdimenzionalnom veli£inom, Re, koja se naziva Reynoldsov broj. Op¢e-nito, Re = ρdv/µ pri £emu je: ρ gusto¢a �uida, d karakteristi£na duljina, vbrzina �uida, a µ viskoznost �uida.

Kada je Re ≪ 1 dominiraju viskozne sile te je sila otpora na sferni oblikradijusa r pribliºno linearna s brzinom: Fd = −6πµrv. Ova aproksimacijanaziva se Stokesova aproksimacija. Kada je Re > 103 dominiraju inercijalnesile i sila otpora je pribliºno pribliºno proporcionalna kvadratu brzine. Re-alisti£ni Reynoldsovi brojevi se kre¢u od 0(1) za £esticu pra²ine u zraku do108 za podmornicu u vodi.

Kako bi odredili koji od modela je najprikladniji za £ovjeka koji pada krozatmosferu dovoljno je procijeniti Reynoldsov broj. Gusto¢a ρ i viskoznost µsu prakti£ki konstantne na visinama prikladnima za skakanje padobranom:ρ ≈ 1 kg/m3, µ ≈ 1.5 × 10−5 kg/(m/s). Koristimo terminalnu brzinu kaokarakteristi£nu brzinu. Prilikom zavr²etka skoka terminalna brzina je uspo-rediva sa skokom sa 1.5 m visokog zida, v ≈ 5.3 m/s. Realisti£na terminalnabrzina tijekom slobodnog pada iznosi v ≈ 45m/s. Tipi£na procjena za karak-teristi£nu duljinu d u toku je promjer diska koji predstavlja popre£an presjeku toku. Potpuno otvoren padobran predstavlja popre£ni presjek A ≈ 44 m2

²to daje d ≈ 7.5 m dok padobranac ra²irenih ruku i nogu u stabilnoj for-maciji predstavlja popre£ni presjek A ≈ 0.5 m2 ²to daje d ≈ 0.8 m. Iz togadobivamo da je i prije i poslije otvaranja padobrana Re > 106 pa ne moºemokoristiti Stokesovu aproksimaciju.

Za Reynoldsov broj Re > 103 otpor na tijelo koje predstavlja popre£nipresjek A u toku moºe biti modeliran pomo¢u:

Fd =1

2CdAρv

2

Koe�cijent otpora Cd odre�en je oblikom tijela:

11

Oblik Reynoldsov broj Cd

Polusferi£na ljuska Re > 103 1.33Disk Re > 103 1.10Ravna traka Re > 103 1.95Valjak 103 < Re < 2× 105 1.95

Re > 5× 105 ≈ 0.35Sfera 103 < Re < 2× 105 0.45

Re > 3× 105 ≈ 0.20

Sila otpora nastaje zbog tijela padobranca, suspenzijskih linija na padobranute kupole padobrana. Otvaranje padobrana se moºe prikazati u nekoliko faza.U trenutku t0 povla£i se uºe za otvaranje padobrana te se suspenzijske linijeizvla£e. U trenutku kada su suspenzijske linije u potpunosti izvu£ene, t = t1,trzajna sila djeluje na padobranca te on iz stabilne formacije sa ra²irenimrukama i nogama prelazi u uspravan poloºaj. Kupola padobrana se po£injenapuhavati te je u trenutku t = t2 u potpunosti napuhana. Zbog momentaokolne zra£ne mase dolazi do hiperin�acije kupole te njenog vra¢anja u sta-nje u kojem je bila u trenutku t = t2. Hiperin�acija i dezin�acija su gotoviu trenutku t = t3 nakon £ega se dalje normalno nastavlja spu²tanje pado-branom. Ukupno vrijeme za otvaranje padobrana iznosi 3.2 s pri £emu jet1 − t0 = 0.5 s, t2 − t1 = 1.0 s i t3 − t2 = 1.7 s.

Tijelo padobranca i oprema stvaraju zasebne sile otpora pri £emu jeukupna sila otpora:

Fd = F bd + F e

d =1

2ρ(Cb

dAb + Ce

dAe)v2 (1.3)

gdje b ozna£ava tijelo (body), a e opremu (equipment). Ovaj model zane-maruje silu otpora koja nastaje zbog suspenzijskih linija i pretpostavlja dasu tijelo i oprema rigidno povezani. U stvarnosti suspenzijske linije stvarajuotpor i cijeli sustav je elasti£an.

Prilikom daljnjeg razmatranja modela koristimo nekoliko veli£ina. Po-pre£ni presjek padobrana iznosi a1 = 43.8 m2. Suspenzijske linije duga£kesu l = 8.96 m. Tipi£an padobranac u uspravnom poloºaju moºe se prikazatikao valjak visine h = 1.78 m sa povr²inom popre£nog presjeka b1 = 0.1 m2.Tijekom slobodnog pada taj poloºaj je nestabilan i te²ko ga je odrºati duljeod nekoliko sekundi. U stabilnom poloºaju sa ra²irenim rukama i nogamatijelo padobranca se moºe prikazati kao tanka pravokutna traka povr²ineb0 = 0.5 m2. Sveukupno imamo:

a1 b0 b1 h l43.8 m2 0.5 m2 0.1 m2 1.78 m 8.96 m

12

Parametri u formuli (1.3) mijenjaju se ovisno o vremenu tj. fazi skoka:

Ab(t) =

b0 , t ≤ t0b0 , t0 ≤ t ≤ t1b1 , t1 ≤ t ≤ t2b1 , t2 ≤ t ≤ t3b0 , t ≤ t0

Cbd(t) =

1.95 , t ≤ t01.95 , t0 ≤ t ≤ t10.35× h , t1 ≤ t ≤ t20.35× h , t2 ≤ t ≤ t30.35× h , t ≤ t0

Ae(t) =

0.0 , t ≤ t0b1 , t0 ≤ t ≤ t1Ae

1,2(t) , t1 ≤ t ≤ t2Ae

2,3(t) , t2 ≤ t ≤ t3a1 , t ≤ t0

Ced(t) =

0.0 , t ≤ t00.35× l t−t0

t1−t0, t0 ≤ t ≤ t1

1.33× h , t1 ≤ t ≤ t21.33× h , t2 ≤ t ≤ t31.33× h , t ≤ t0

Modi�ciramo na² inicijalni model:

F = mg − kv2

tako ²to uzimamo:

k = 12ρ(Cb

dAb + Ce

dAe)

k = 12ρ

1.95b0 , t ≤ t01.95b0 + 0.35b1l

t−t0t1−t0

, t0 ≤ t ≤ t10.35b1h+ 1.33Ae

1,2(t) , t1 ≤ t ≤ t20.35b1h+ 1.33Ae

2,3(t) , t2 ≤ t ≤ t30.35b1h+ 1.33a1 , t ≤ t0

pri £emu su Ae1,2(t) i A

e2,3(t) jednadºbe koje govore kako se mijenja povr²ina

padobrana u odgovaraju¢im vremenskim intervalima; [t1, t2] i [t2, t3]:

Ae1,2(t) = α0e

β0

(t−t1t2−t1

)

Ae2,3(t) = a1

(1 + β1 sin

(πt− t2t3 − t2

))gdje su:

α0 =1.95b0 + 0.35b1(1− h)

1.33

β0 = log(a1α0

)β1 = 0.15

13

Sada kona£no imamo model koji je �zikalno vjerodostojan te ga ujedno mo-ºemo bez problema koristiti kod numeri£kih prora£una u simulaciji. Sve ²tomoramo izmijeniti u numeri£koj implementaciji je prora£unavanje akcelera-cije tj. koe�cijenta otpora k ovisno o tome u kojoj se fazi skoka nalazimo.Primjer prora£una dobivenih kori²tenjem ovog modela moºe se vidjeti u slje-de¢im grafovima dobivenima izravno iz simulacije:

0 5 10 15 20Vrijeme [s]

−10

0

10

20

30

40

50

Akc

ele

raci

ja [

m/(

s*s)

]

Slika 1.1: Graf: akceleracija u vremenu

14

0 5 10 15 20Vrijeme [s]

−45

−40

−35

−30

−25

−20

−15

−10

−5

0B

rzin

a [

m/s

]

Slika 1.2: Graf: brzina u vremenu

0 5 10 15 20Vrijeme [s]

−50

0

50

100

150

200

250

300

350

400

Polo

zaj [m

]

Slika 1.3: Graf: poloºaj u vremenu

15

Poglavlje 2

Izrada simulacije

2.1 Ciljevi izrade

Prije nego li krenemo sa izradom simulacije moramo znati ²to to£no ºelimoposti¢i. Uz simuliranje skoka s padobranom sa otporom zraka logi£no se na-dovezuje i simuliranje istog slu£aja bez otpora zraka. Ukoliko istovremenopromatramo skok dva padobranca, pri £emu simuliramo utjecaj vjetra nasamo jednog od njih, pruºa nam se zgodna prilika za demonstriranje prin-cipa neovisnosti gibanja. Bilo bi poºeljno imati mogu¢nost gra�£ke analizeizravnih prora£una kao i simuliranih skokova. Aplikacija mora biti robusna,imati jednostavno su£elje i nuditi razli£ite mogu¢nosti prilagodbe.

�eljene mogu¢nosti aplikacije:

� simulacija skoka sa otporom zraka

� simulacija skoka bez otpora zraka

� simulacija principa neovisnosti gibanja

� gra�£ka analiza obavljene simulacije

� gra�£ka analiza izravnog prora£una

� podesivost opcija prikaza

� podesivost �zikalnih varijabli

� robusnost pri unosu podataka

� jednostavno i pregledno korisni£ko su£elje

16

2.2 Alati za izradu

Prilikom izrade simulacije kori²teno je mno²tvo razli£itih alata. Srº simulacije£ini Panda3D engine, biblioteka metoda za 3D prikaz i razvoj aplikacija.Moºe se koristiti u C++ ili Python programima, ovdje koristimo Pythonjer je razvoj aplikacija u njemu ne²to lak²i. Panda je stvorena za razvojkomercijalnih igara: optimizirana je za brzo izvo�enje, sadrºi kompletan nizpotrebnih alata za razvoj i veoma je stabilna.

Bitno je napomenuti kako Panda nije po£etni£ki alat te je potrebno odre-�eno informati£ko predznanje kako bi se pomo¢u nje uspje²no razvijale apli-kacije. Prilikom opisa izrade simulacije biti ¢e opisan op¢enit tok izrade none i kori²tene metode - one su opisane u online priru£niku i API dokumen-taciji [4] koja je prilikom izrade ovog rada obilno kori²tena. Za sve aspekteprogramiranja vezane uz Python od iznimne koristi bila je Python onlinedokumentacija [5].

Simulacija je ra�ena u NetBeans okruºenju koje podrºava Python. Zamale i brze izmjene kori²ten je ure�iva£ teksta Notepad++. Prilikom izradei modi�kacije korisni£kog su£elja i 3D modela kori²teni su GIMP i Blender.Iscrtavanje grafova u simulaciji omogu¢eno je pomo¢u NumPy i matplotlibbiblioteka za Python.

Ina£ice svih kori²tenih aplikacija i biblioteka:

� Panda3D SDK 1.7.2

� Python 2.6.5

� NumPy 1.6.1

� matplotlib 1.1.0

� NetBeans IDE 6.9.1

� Notepad++ v5.9.6.2

� GIMP 2.6.11

� Blender 2.60

2.3 Princip funkcioniranja Panda3D enginea

Panda3D je kompleksan alat i opisivanje svih principa funkcioniranja je izvanopsega ovog rada. Ovdje ¢emo okvirno prikazati samo neke osnovne principekoji se koriste prilikom izrade simulacije.

17

Kada kreiramo aplikaciju pomo¢u Pande ve¢inom se sve svodi na poziva-nje speci�£nih klasa i njihovih metoda pri £emu korisnik ne zna sve detaljeimplementacije. Tako su, npr., dovoljne samo 3 naredbe kako bi stvoriliprozor aplikacije:

from direct.showbase.DirectObject import DirectObjectimport direct.directbase.DirectStartrun()

Na ovaj na£in se vrlo jednostavno dobije ne²to ²to bi ina£e bilo veoma slo-ºeno za napraviti, korisnik u ovom speci�£nom slu£aju uop¢e ne mora voditira£una o pojedinostima operativnog sustava i hardwarea koji koristi.

DirectObject je ina£e klasa koja se skupa sa svojim potklasama eksten-zivno koristi prilikom izrade aplikacija u Pandi. Kori²tenjem API dokumen-tacije [4] moºemo vidjeti njen dijagram naslje�ivanja:

Slika 2.1: Dijagram naslje�ivanja klase DirectObject

API dokumentacija tako�er sadrºi i popis javnih metoda klase (kao i njihovdetaljniji opis):

def __init__def acceptdef acceptOncedef addTaskdef classTreedef detectLeaksdef doMethodLaterdef getAllAcceptingdef ignoredef ignoreAlldef isAcceptingdef isIgnoringdef removeAllTasksdef removeTask

18

Za uspje²an rad u Pandi bitno je poznavanje na£ina manipulacije razli£itimobjektima za ²to je dobrim dijelom zaduºena klasa PandaNode:

Slika 2.2: Razli£iti podtipovi klase PandaNode

Kori²tenjem podtipova klase PandaNode moºemo stvarati kompleksnu oko-linu i scene. Kada Panda prikazuje simulaciju na zaslonu prilikom iscrtavanjakoristi graf scene, stablo koje se sastoji od raznovrsnih podtipova PandaNode-ova nad kojima se vr²e potrebne manipulacije.

Slika 2.3: Primjer grafa scene

Budu¢i da je ovo samo okviran pregled za detaljnije razumijevanje ovih i

19

ostalih principa funkcioniranje Pande obavezno je prou£avanje njene opseºnedokumentacije [4].

2.4 Kreiranje okoline

Bitna stvar svake simulacije je okolina u kojoj se simulacija odvija. Oko-lina mora sadrºavati sve potrebne elemente za simuliranje pojave pri £emuje poºeljno da elementi budu vizualno atraktivni no ne i previ²e ra£unalnozahtjevni. Jednostavna okolina koju koristimo sastoji se od:

� animiranog modela padobranca

� modela zra£nog balona

� modela neba

� modela palminog stabla

� teksturiranog poda

Rezultat koji dobijemo uporabom navedenih elemenata izgleda ovako:

Slika 2.4: Izgled okoline iz udaljene perspektive

Korisnik aplikacije, naravno, ima sasvim druk£iju perspektivu u kojoj se nevide ograni£enja i konstrukcija okoline ve¢ sve djeluje uvjerljivo.

Svi kori²teni modeli nastali su ekstenzivnom modi�kacijom besplatnihmodela preuzetih sa interneta. Modeli su obra�ivani u Blenderu te su iznjega izvezeni u DirectX formatu. Taj format je potom konvertiran u Egg

20

format, nativni format koji koristi Panda. Konverzija je obavljena pomo¢ux2egg alata koji dolazi sa instalacijom Pande. Iako postoje skripte za Blenderkoje omogu¢avaju izravno pohranjivanje modela u Egg format, one naºalost utrenutku izrade nisu bile dostupne za kori²tenu ina£icu Blendera pa je ovakavpostupak konverzije bio neophodan.

Posebnu modi�kaciju zahtijevao je model padobranca. Osim kozmeti£-kih izmjena te izrade i dodavanja padobrana bilo ga je nuºno i animirati uBlenderu. Postupak se ugrubo sastoji od kreiranja kostura modela, odre�i-vanja dijela modela koji odre�ena kost kontrolira i manipuliranja gotovimkosturom. Prilikom animiranja modela vo�ena je paºnja o svakoj fazi skokas padobranom, naro£ito o fazi otvaranja padobrana gdje su precizno tem-pirane animacije otvaranja, hiperin�acije i dezin�acije. Duljina kompletneanimacije skoka s padobranom iznosi 582 sli£ice pri £emu je brzina izvo�enjaanimacije 60 sli£ica u sekundi. Tijekom odre�enog broja sli£ica u animacijimodel samo miruje i te sli£ice koristimo u simulaciji kako bi stvorili prividmirovanja padobranca tako ²to prikazujemo animaciju u speci�£nom inter-valu koji nam odgovara.

Slika 2.5: Dio procesa animiranja u Blenderu - kostur modela

Postupak konvertiranja padobranca u Egg format se tako�er malo razlikujeod konvertiranja ostalih modela. Budu¢i da uz model imamo i animacijumoramo se pobrinuti da prilikom izvoºenja modela u DirectX format oda-beremo i opciju izvoºenja svake sli£ice animacije. Prilikom kori²tenja x2eggalata koristimo -model i -anim parametre te tako stvaramo dvije zasebneEgg datoteke, jednu koja sadrºi model i drugu koja sadrºi samo animaciju.Iako je u Pandi potencijalno mogu¢e imati i animaciju i model u jednoj Eggdatoteci ovaj na£in je puno sigurniji.

21

Jednom kada imamo sve modele i animacije u Egg formatu moºemo ihbez problema koristiti u Pandi. Panda omogu¢ava da se unutar simulacijenad svakim od modela vr²e manipulacije poput pozicioniranja, orijentiranja,promjene veli£ine. . . Prilikom kori²tenja modela palme sluºimo se Pandinommetodom instanciranja. Ukratko, umjesto u£itavanja i prikazivanja punoistih modela u£itava se samo jedan model i prikazuje vi²e puta. Na ovaj na£in²tede se ra£unalni resursi, a ne gubi se funkcionalnost s obzirom da i daljemoºemo manipulirati svakim prikazom kao da manipuliramo sa zasebnimmodelima. Instanciranjem palmi na zgodan na£in razbijamo monotonostpoda.

Pod koji koristimo u okolini je u principu samo slika tj. tekstura pijeskakoju ponavljamo u odre�enim intervalima. Pritom je bitno napomenuti da jetekstura zapravo postavljena na jednostavan model kvadrata ²to omogu¢ujeda s njom manipuliramo na puno vi²e na£ina nego ²to bi to mogli sa samomteksturom bez modela.

Ukoliko ºelimo simulirati princip neovisnosti gibanja potrebne su mini-malne promjene u okolini. Sve ²to trebamo napraviti je dodati dva razli£itapadobranca te promijeniti teksturu poda. Budu¢i da bi nam vizualno koris-tila neka vrsta referentne to£ke umjesto bezli£ne teksture pijeska koristimoupe£atljivu sliku mete pri £emu vi²e ne instanciramo palme. Meta se vizualnosastoji od koncentri£nih kruºnica koje pomaºu pri ocjeni udaljenosti izme�upadobranaca.

Kori²tenjem svih navedenih elemenata okolina je u potpunosti dovr²ena.Dobar primjer naredbi koje se koriste tijekom kreiranja okoline moºe se vidjetiu metodi createEnvironment na 1983. liniji izvornog koda.

2.5 Kreiranje doga�aja

Klju£ svake simulacije £ine doga�aji, nad nekima korisnici imaju kontroludok se drugi odvijaju u pozadini prema prethodno de�niranim pravilima.Ukoliko doga�aji nisu ispravno postavljeni ni najbolje prezentirana okolinane¢e biti od koristi u simulaciji.

Korisnik tijekom simulacije upravlja sljede¢im doga�ajima:

� aktivacija skoka s padobranom

� aktivacija otvaranja padobrana

� kontrola kamere

� kontrola vjetra

22

� izlaz iz simulacije

Prilikom svakog od tih doga�aja izvodi se niz odgovaraju¢ih metoda. Da biimplementirali doga�aje obilno koristimo Pandine taskMgr i task objekte.Ti objekti nam omogu¢uju da odaberemo kad ¢emo to£no pozvati ºeljenemetode te se potom te metode izvr²avaju sve dok ih ne uklonimo ili se neispune odre�eni uvjeti. TaskMgr pri svakom novom iscrtavanju slike na zas-lonu poziva sve metode koje su mu predane. Upravo ovo je presudan detaljkoji koristimo prilikom numeri£ke implementacije �zikalnog modela. Naime,uporabom podatka o vremenu koje je proteklo izme�u dva iscrtavanja slikena zaslonu sinkroniziramo prora£un �zikalnog modela. Primjer toga je me-toda jumpBO na 2133. liniji izvornog koda koja obavlja prora£un sve dopo£etka otvaranja padobrana.

Kako bi korisnik uop¢e mogao pokrenuti neki doga�aj moramo na nekakavna£in omogu¢iti aktivaciju doga�aja pomo¢u unosa sa tipkovnice ili mi²a. Topostiºemo uporabom Pandine metode accept koja prihva¢a dva argumenta,tipku unosa i metodu koja ¢e se pokrenuti prilikom pritiska te tipke. Izvrstanprimjer aktivacije unosa tom metodom je metoda enableControls na 1335.liniji izvornog koda.

Svi doga�aji u simulaciji su ostvareni uporabom taskMgr i task obje-kata te accept metode. Uz doga�aje koje kontrolira korisnik postoji jo² nizdoga�aja koji se izvode u pozadini. Ovo je pregled nekih od njih:

Naziv metode Svrha metode Linija u izv. kodujumpBO prora£un prije otvaranja padobrana 2133jumpAO prora£un nakon otvaranja padobrana 2153noDrag prora£un bez otpora zraka 2320

2.6 Kreiranje korisni£kog su£elja

Panda omogu¢ava jednostavnu izradu korisni£kih su£elja pomo¢u razli£itihvrsta DirectGui objekata. Naj£e²¢e kori²ten objekt je DirectButton koji sluºiza kreiranje gumba koji ¢e nakon ²to na njega kliknemo mi²em pozvati pret-hodno speci�ciranu metodu. Za slu£ajeve gdje nam je potreban odabir op-cija koriste se DirectCheckButton i DirectRadioButton objekti koji korisnikuprikazuju da li je neka opcija uklju£ena ili isklju£ena te koja je to£no opcijaodabrana ukoliko ih je ponu�eno vi²e.

Prilikom unosa podataka sa tipkovnice koristi se DirectEntry objekt.Kako bi se osigurala robusnost aplikacije svaki korisnikov unos prolazi kroztip Pythonove try-except izjave, primjer toga moºe se vidjeti u funkcijisetSimulationMass na 96. liniji izvornog koda. Odre�eni korisni£ki unosi

23

i odabiri koji se ti£u postavki prikaza zapisuju se u vanjsku datoteku kojase £ita prilikom pokretanja aplikacije kako bi se opcije postavile na ºeljenuvrijednost.

Svi DirectGui objekti imaju niz opcija koje se mogu podesiti. Prilikomizrade korisni£kog su£elja za ovu aplikaciju veoma korisnom se pokazala mo-gu¢nost kori²tenja proizvoljnih tekstura na DirectGui objektima. Teksturegumbi i pozadina su£elja su napravljeni pomo¢u programa GIMP. Svi na-vedeni DirectGui elementi i njihove modi�kacije mogu se vidjeti u nekolikoklasa u koje je podijeljeno korisni£ko su£elje:

Segment su£elja Linija u izvornom koduglavni izbornik 184izbornik gra�£ke analize 267izbornik postavki simulacije 393izbornik postavki analize 496izbornik postavki prikaza 617izbornik sa informacijama 924

2.7 Kreiranje i pakiranje aplikacije

Jednom kada imamo sve nuºne elemente simulacije potrebno ih je stavitiu cjelinu. Postoji mnogo razli£itih na£ina na koje je to mogu¢e u£initi. Usu²tini, cijeli program se sastoji od nekoliko klasa koje su zaduºene za koris-ni£ko su£elje, jedne ogromne klase u kojoj je simulacija te nekoliko manjihglobalnih funkcija i varijabli koje sluºe za komunikaciju izme�u klasa.

Program je zami²ljen tako da se na po£etku u£ita kompletno korisni£kosu£elje te svi modeli koji ¢e se potencijalno koristiti u simulaciji. Pritomse u£itani modeli dijele na razli£ite grane grafa scene i skriva ih se sve dokne budu potrebni. Op¢enito gledano, u£itavanje svih modela nije optimalanpristup no budu¢i da imamo jako mali broj modela te memorijsko zauze¢enije veliko moºemo bez problema napraviti implementaciju tim pristupom.Konkretnu implementaciju mogu¢e je vidjeti u konstruktoru klase Simulationna 984. liniji izvornog koda.

Bitan aspekt svakog programa je upravljanje ra£unalnom memorijom.Iako je obi£aj pisati destruktore koji su prilago�eni programu Panda ima nizspeci�£nosti te se preporu£uje da ona sama oslobodi memoriju koju vi²e nekoristi. Naravno, uvijek je mogu¢e ru£no pozvati Pythonov garbage collectormodul kada je to potrebno. Garbage collector se poziva svaki put kada sezapo£inje nova simulacija ili se izlazi iz izbornika za gra�£ku analizu. Razlogtome je ²to prora£uni i grafovi mogu zauzeti jako puno memorije te ukoliko

24

se ta memorija nakon kori²tenja ne oslobodi vrlo brzo moºe do¢i do velikihproblema.

Panda nudi nekoliko razli£itih na£ina za kreiranje aplikacija iz gotovihprograma. Za kreiranje ove aplikacije kori²ten je packpanda alat koji stvarakompletnu instalaciju aplikacije uz koju dolazi i distribucija Panda3D enginea£ime je osigurano da aplikacija radi na svakom ra£unalu bez potrebe za do-datnim datotekama. Bitno je napomenuti kako je prije kori²tenja programapackpanda poºeljno modi�cirati packpanda.nsi skriptu tako da se u njoj za-mijene sve instance poziva na ppython.exe sa pozivom na ppythonw.exe.

Naime, uobi£ajeno pona²anje Pande je da tijekom izvo�enja programabude otvoren command prompt prozor na kojem se ispisuju poruke o izvo�e-nju programa. Ovo moºe dovesti do nepotrebne konfuzije krajnjeg korisnikapa je zgodno maknuti taj prozor. Navedenom modi�kacijom packpanda.nsiskripte postiºemo upravo taj efekt. Kreiranjem aplikacije pomo¢u packpandaalata kona£no zavr²ava i cjelokupni proces izrade simulacije.

25

Mogu¢nosti uporabe u nastavi

�zike i informatike

Aplikacija napravljena u ovom radu ima razli£ite potencijalne primjene. Vje-rojatno najbolji na£in uporabe bio bi da se aplikacija da u£enicima na kori-²tenje u sklopu istraºiva£kog rada (seminara ili referata) u kojem bi u£enicisami razmatrali slobodan pad i utjecaj otpora zraka. Pritom se od u£enikamoºe traºiti da naprave niz procjena poput odre�ivanja minimalne visine zasiguran doskok, prosje£nog trajanja skoka sa neke visine te odre�ivanja koe�-cijenta otpora pomo¢u izraza za terminalnu brzinu. Aplikacija moºe posluºitii tijekom nastave kada se obja²njava princip neovisnosti gibanja te tijekomobra�ivanja slobodnog pada kako bi se okvirno demonstrirao u£inak otporazraka.

Potencijalnu korist mogli bi imati i studenti informatike. Aplikacija moºeposluºiti kao tehnolo²ki demonstrator, a budu¢i da je izvorni kod dan nakori²tenje pod open source licencom dijelovi aplikacije potencijalno moguposluºiti i kao kostur za stvaranje neke nove aplikacije.

26

Zaklju£ak

Napretkom tehnologije konstantno se pruºaju nove mogu¢nosti u odrºavanjunastave �zike. Kori²tenjem ra£unalnih simulacija mogu se prikazati pojavei pokusi koje ina£e ne bi bilo mogu¢e izvesti u u£ionici, bilo zbog sloºenostiili manjka speci�£ne opreme. U£enici £esto imaju ulogu pasivnih proma-tra£a ²to se kori²tenjem simulacija moºe donekle promijeniti. Danas velikave¢ina u£enika posjeduje pristup ra£unalu te su u mogu¢nosti da sami koristesimulacije i istraºuju pojave prikazane u njima.

Kori²tenjem 3D engine-a simulacije vi²e ne moraju biti samo �zikalnovjerodostojne ve¢ mogu biti i vizualno zorne i atraktivne ²to moºe dodatnopove¢ati interes u£enika za njihovo kori²tenje. Potencijalno se pruºa mogu¢-nost da ono ²to u£enicima na prvi pogled izgleda kao mukotrpan rad postanezanimljiva igra istraºivanja. Iako rad sa 3D engine-ima nije trivijalan mogu¢-nosti su brojne te se nadam da ¢e u budu¢nosti vi²e simulacija biti izra�enopomo¢u njih.

27

Izvorni kod

1 # Skydiver 1.0 / Padobranac 1.0

2 # Last revision / Posljednja revizija: 27.12.2011

3 # Copyright (C) <2011> <Goran Jambresic>

4 # Contact: [email protected]

5 #

6 # This program is free software: you can redistribute it and/or modify

7 # it under the terms of the GNU General Public License as published by

8 # the Free Software Foundation, either version 3 of the License, or

9 # (at your option) any later version.

10 #

11 # This program is distributed in the hope that it will be useful,

12 # but WITHOUT ANY WARRANTY; without even the implied warranty of

13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

14 # GNU General Public License for more details.

15 #

16 # You should have received a copy of the GNU General Public License

17 # along with this program. If not, see <http://www.gnu.org/licenses/

18

19

20 # Importing all the necessary modules and libraries and creating a window.

21 # Ucitavanje svih potrebnih modula i biblioteka te stvaranje prozora.

22

23 from direct.showbase.DirectObject import DirectObject

24 from direct.actor.Actor import Actor

25 from direct.interval.IntervalGlobal import *

26 from direct.gui.OnscreenImage import OnscreenImage

27 from direct.gui.OnscreenText import OnscreenText

28 from direct.gui.DirectGui import *

29 from pandac.PandaModules import *

30

31 import math

32 import sys

33 import random

34 import matplotlib.pyplot as plt

35 import gc

36

37 gc.enable()

38

28

39 loadPrcFileData("", "win-size 320 240")

40 loadPrcFileData("", "window-title Padobranac 1.0")

41

42 import direct.directbase.DirectStart

43

44 # Loading of data which is used in creating menus.

45 # Ucitavanje podataka koji se koriste prilikom stvaranja izbornika.

46

47 img = ("menu/bigbutton2.png", "menu/bigbutton7.png", "menu/bigbutton7.png",

48 "menu/bigbutton7.png")

49 img2 = ("menu/bigbutton4.png", "menu/bigbutton4.png", "menu/bigbutton4.png",

50 "menu/bigbutton4.png")

51 img3 = ("menu/bigbutton5.png", "menu/bigbutton6.png", "menu/bigbutton6.png",

52 "menu/bigbutton6.png")

53 img4 = ("menu/bigbutton7.png", "menu/bigbutton2.png", "menu/bigbutton2.png",

54 "menu/bigbutton2.png")

55

56 # Global variables which are used to store program settings shared between

57 # various classes and methods.

58 # Globalne varijable koje se koriste kako bi se pohranile postavke programa

59 # koje se dijele izmedu razlicitih klasa i metoda.

60

61 screenWidth = 800

62 screenHeight = 600

63 showFPScounter = False

64 screenStateFull = False

65 TwoSkydiversOn = False

66 DragOff = False

67 SimulationStateIndicator = [1]

68 SimulationMass = 97.2

69 SimulationHeight = 1500

70 AnalysisStateIndicator = [1]

71 AnalysisMass = 97.2

72 AnalysisHeight = 1500

73 TimeOfOpening = 10

74

75 # Various functions used to set global variables.

76 # Razne funkcije koje se koriste za postavljanje globalnih varijabli.

77

78 def setSimulationState1():

79 global TwoSkydiversOn

80 TwoSkydiversOn = False

81 global DragOff

82 DragOff = False

83

84 def setSimulationState2():

85 global TwoSkydiversOn

86 TwoSkydiversOn = False

87 global DragOff

29

88 DragOff = True

89

90 def setSimulationState3():

91 global TwoSkydiversOn

92 TwoSkydiversOn = True

93 global DragOff

94 DragOff = False

95

96 def setSimulationMass(text):

97 global SimulationMass

98 try:

99 SimulationMass = float(text)

100 except:

101 SimulationMass = 97.2

102 if (SimulationMass < 50):

103 SimulationMass = 50

104 if (SimulationMass > 150):

105 SimulationMass = 150

106

107 def setSimulationHeight(text):

108 global SimulationHeight

109 try:

110 SimulationHeight = float(text)

111 except:

112 SimulationHeight = 1500

113 if (SimulationHeight < 100):

114 SimulationHeight = 100

115 if (SimulationHeight > 1500):

116 SimulationHeight = 1500

117

118 def setAnalysisMass(text):

119 global AnalysisMass

120 try:

121 AnalysisMass = float(text)

122 except:

123 AnalysisMass = 97.2

124 if (AnalysisMass < 0.1):

125 AnalysisMass = 0.1

126 if (AnalysisMass > 1000):

127 AnalysisMass = 1000

128

129 def setAnalysisHeight(text):

130 global AnalysisHeight

131 try:

132 AnalysisHeight = float(text)

133 except:

134 AnalysisHeight = 1500

135 if (AnalysisHeight < 0.1):

136 AnalysisHeight = 0.1

30

137 if (AnalysisHeight > 15000):

138 AnalysisHeight = 15000

139

140 def setTimeOfOpening(text):

141 global TimeOfOpening

142 try:

143 TimeOfOpening = float(text)

144 except:

145 TimeOfOpening = 10

146 if (TimeOfOpening < 0):

147 TimeOfOpening = 0

148 if (TimeOfOpening > 9999):

149 TimeOfOpening = 9999

150

151 def setCustomWidth(text):

152 global screenWidth

153 try:

154 screenWidth = int(text)

155 except:

156 screenWidth = 800

157 if (screenWidth < 320):

158 screenWidth = 320

159 if (screenWidth > 9999):

160 screenWidth = 9999

161

162 def setCustomHeight(text):

163 global screenHeight

164 try:

165 screenHeight = int(text)

166 except:

167 screenHeight = 600

168 if (screenHeight < 240):

169 screenHeight = 240

170 if (screenHeight > 9999):

171 screenHeight = 9999

172

173 def setFPScounter(state):

174 global showFPScounter

175 if (state==1):

176 showFPScounter = True

177 else:

178 showFPScounter = False

179

180

181 # A class for creating main menu.

182 # Klasa za kreiranje glavnog izbornika.

183

184 class MainMenu:

185 def __init__(self, parent):

31

186 self.mainFrame = DirectFrame(frameColor = (1,1,1,1),

187 frameSize = (-0.001,0.001,-0.001,0.001),

188 relief = None)

189 self.pos_z = 0.45

190 self.gui_elem_1 = DirectButton(parent = self.mainFrame, image = img,

191 image_pos = (0,0,0.25),

192 image_scale = (5,1,1), relief = None,

193 text = ("Simulacija skoka"),

194 pos = (0,0,self.pos_z), scale = 0.05,

195 rolloverSound = None, clickSound = None,

196 command = parent.startSim)

197 self.gui_elem_2 = DirectButton(parent = self.mainFrame, image = img,

198 image_pos = (0,0,0.25),

199 image_scale = (5,1,1), relief = None,

200 text = ("Graficka analiza"),

201 pos = (0,0,self.offsetZ()), scale = 0.05,

202 rolloverSound = None, clickSound = None,

203 command = parent.options1)

204 self.gui_elem_3 = DirectButton(parent = self.mainFrame, image = img,

205 image_pos = (0,0,0.25),

206 image_scale = (5,1,1),

207 relief = None,

208 text = ("Postavke simulacije"),

209 pos = (0,0,self.offsetZ()), scale = 0.05,

210 rolloverSound = None, clickSound = None,

211 command = parent.options2)

212 self.gui_elem_4 = DirectButton(parent = self.mainFrame, image = img,

213 image_pos = (0,0,0.25),

214 image_scale = (5,1,1), relief = None,

215 text = ("Postavke analize"),

216 pos = (0,0,self.offsetZ()), scale = 0.05,

217 rolloverSound = None, clickSound = None,

218 command = parent.options3)

219 self.gui_elem_5 = DirectButton(parent = self.mainFrame, image = img,

220 image_pos = (0,0,0.25),

221 image_scale = (5,1,1), relief = None,

222 text = ("Vizualne postavke"),

223 pos = (0,0,self.offsetZ()), scale = 0.05,

224 rolloverSound = None, clickSound = None,

225 command = parent.options4)

226 self.gui_elem_6 = DirectButton(parent = self.mainFrame, image = img,

227 image_pos = (0,0,0.25),

228 image_scale = (5,1,1),

229 relief = None, text = ("Info"),

230 pos = (0,0,self.offsetZ()),

231 scale = 0.05, rolloverSound = None,

232 clickSound = None,

233 command = parent.options5)

234 self.gui_elem_7 = DirectButton(parent = self.mainFrame, image = img,

32

235 image_pos = (0,0,0.25),

236 image_scale = (5,1,1), relief = None,

237 text = ("Izlaz"),

238 pos = (0,0,self.offsetZ()), scale = 0.05,

239 rolloverSound = None, clickSound = None,

240 command = parent.exit)

241

242 # A method for hiding the menu.

243 # Metoda za skrivanje izbornika.

244

245 def hide(self):

246 self.mainFrame.hide()

247

248 # A method for showing the menu.

249 # Metoda za prikazivanje izbornika.

250

251 def show(self):

252 self.mainFrame.show()

253

254 # This is a method used when positioning menu items and/or text. Same

255 # method can be found in other similar classes.

256 # Ovo je metoda koja se koristi prilikom pozicioniranja elemenata

257 # izbornika i/ili teksta. Ista metoda se moze pronaci u drugim slicnim klasama.

258

259 def offsetZ(self):

260 self.pos_z -= 0.125

261 return self.pos_z

262

263

264 # A class for creating graphical analysis menu.

265 # Klasa za kreiranje izbornika graficke analize.

266

267 class GraphicalAnalysis(DirectObject):

268 def __init__(self, parent):

269 self.parent = parent

270 self.mainFrame = DirectFrame(frameColor = (1,1,1,1),

271 frameSize = (-0.001,0.001,-0.001,0.001),

272 relief = None)

273 self.pos_z = 0.45

274 self.gui_elem_1 = DirectLabel(parent = self.mainFrame,

275 text = "Informacije o skoku",

276 pos = (0,0,self.pos_z), scale = 0.05,

277 textMayChange = 0, frameColor = (0,0,0,0))

278 self.gui_elem_2 = DirectLabel(parent = self.mainFrame, text = "",

279 pos = (0,0,self.offsetZ()), scale = 0.05,

280 textMayChange = 1, frameColor = (0,0,0,0))

281 self.gui_elem_3 = DirectLabel(parent = self.mainFrame, text = "",

282 pos = (0,0,self.offsetZ()), scale = 0.05,

283 textMayChange = 1, frameColor = (0,0,0,0))

33

284 self.gui_elem_4 = DirectLabel(parent = self.mainFrame, text = "",

285 pos = (0,0,self.offsetZ()), scale = 0.05,

286 textMayChange = 1, frameColor = (0,0,0,0))

287 self.gui_elem_5 = DirectButton(parent = self.mainFrame, image = img,

288 image_pos = (0,0,0.25),

289 image_scale = (5,1,1), relief = None,

290 text = ("Prikaz grafova"),

291 pos = (0,0,self.offsetZ()), scale = 0.05,

292 rolloverSound = None, clickSound = None,

293 command = self.showGraphs)

294 self.gui_elem_6 = DirectButton(parent = self.mainFrame, image = img,

295 image_pos = (0,0,0.25),

296 image_scale = (5,1,1), relief = None,

297 text = ("Izlaz"),

298 pos = (0,0,self.offsetZ()), scale = 0.05,

299 rolloverSound = None, clickSound = None,

300 command = parent.gotoMain)

301 self.gui_elem_7 = DirectLabel(parent = self.mainFrame, text = "",

302 pos = (0,0,self.offsetZ()), scale = 0.05,

303 textMayChange = 1, frameColor = (0,0,0,0))

304 self.hide()

305

306 # A method for hiding the menu and clearing no longer used memory.

307 # Metoda za skrivanje izbornika i oslobadanje memorije koja se vise ne koristi.

308

309 def hide(self):

310 try:

311 w.clearMemory()

312 except:

313 pass

314 self.mainFrame.hide()

315

316 # A method for showing the menu and determining which kind of calculations will

317 # be done.

318 # Metoda za prikazivanje izbornika i odredivanje vrste proracuna koji ce se

319 # vrsiti.

320

321 def show(self):

322 if (w.useSimulationData == True):

323 self.gui_elem_2['text'] = "Masa padobranca: "+'%.2f' %(w.m)+" kg"

324 self.gui_elem_3['text'] = "Pocetna visina: "+'%.2f' %(w.height)+" m"

325 if (DragOff == True):

326 self.gui_elem_4['text'] = "Nema otpora zraka"

327 else:

328 self.gui_elem_4['text'] = "Terminalna brzina: "+'%.2f' \

329 %(w.vt) +" m/s"

330 else:

331 self.alreadyCalcualted = False

332 w.initializePhysicsAgain()

34

333 w.m = AnalysisMass

334 w.y = AnalysisHeight

335 w.calculateVT()

336 self.gui_elem_2['text'] = "Masa padobranca: "+'%.2f' \

337 %(AnalysisMass)+" kg"

338 self.gui_elem_3['text'] = "Pocetna visina: "+'%.2f' \

339 %(AnalysisHeight)+" m"

340 if (AnalysisStateIndicator == [1]):

341 self.gui_elem_4['text'] = "Terminalna brzina: "+'%.2f' \

342 %(w.vt)+" m/s"

343 else:

344 self.gui_elem_4['text'] = "Nema otpora zraka"

345 self.gui_elem_7['text'] = ("Napomena: ukoliko ne vrsite analizu vec " +

346 "simuliranog skoka morati\ncete pricekati " +

347 "dok se ne izvrse proracuni. Nakon sto " +

348 "dobijete grafove\nmorate ih zatvoriti " +

349 "kako bi mogli nastaviti raditi u programu.")

350 self.mainFrame.show()

351

352 # A method for showing the graphical results of calculations. Since calculations

353 # can take some time it is ensured that they are only done once in case the user

354 # wants to see graphical results soon after closing them. However, if the user

355 # exits this menu calculations will have to be done again since the memory is

356 # cleared.

357 # Metoda za prikazivanje grafova dobivenih proracunom. Buduci da proracuni mogu

358 # potrajati osigurano je da se rade samo jednom ukoliko korisnik zeli ponovno

359 # vidjeti grafove nakon sto ih je zatvorio. Kada korisnik izade iz izbornika

360 # proracuni se moraju raditi ponovno buduci da se memorija oslobada.

361

362 def showGraphs(self):

363 if (w.useSimulationData == True):

364 w.graphs()

365 elif (AnalysisStateIndicator == [2]):

366 if (self.alreadyCalcualted == False):

367 w.calculateNoDrag()

368 self.alreadyCalcualted = True

369 w.graphs()

370 elif (TimeOfOpening !=0 ):

371 if (self.alreadyCalcualted == False):

372 w.calculate()

373 self.alreadyCalcualted = True

374 w.graphs()

375 else:

376 if (self.alreadyCalcualted == False):

377 w.calculateNoOpening()

378 self.alreadyCalcualted = True

379 w.graphs()

380

381 # A method used when positioning menu items and/or text.

35

382 # Metoda koja se koristi prilikom pozicioniranja elemenata izbornika

383 # i/ili teksta.

384

385 def offsetZ(self):

386 self.pos_z -= 0.125

387 return self.pos_z

388

389

390 # A class for creating simulation settings menu.

391 # Klasa za kreiranje izbornika postavki simulacije.

392

393 class SimulationSettings(DirectObject):

394 def __init__(self, parent):

395 self.parent = parent

396 self.mainFrame = DirectFrame(frameColor = (1,1,1,1),

397 frameSize = (-0.001,0.001,-0.001,0.001),

398 relief = None)

399 self.pos_z = 0.50

400 self.gui_elem_1 = DirectLabel(parent = self.mainFrame,

401 text = "Vrsta simulacije",

402 pos = (0,0,self.pos_z), scale = 0.05,

403 textMayChange = 0, frameColor = (0,0,0,0))

404 self.gui_elem_2 = [

405 DirectRadioButton(parent = self.mainFrame, boxPlacement = 'right',

406 boxImage=img2, image = img, image_pos = (0,0,0.25),

407 image_scale = (7,1,1), relief = None,

408 text = "Standardna simulacija",

409 command = setSimulationState1,

410 variable = SimulationStateIndicator, value = [1],

411 pos = (0,0,self.offsetZ()), scale = 0.05,

412 rolloverSound = None, clickSound = None),

413 DirectRadioButton(parent = self.mainFrame, boxPlacement = 'right',

414 boxImage=img2, image = img, image_pos = (0,0,0.25),

415 image_scale = (7,1,1), relief = None,

416 text = "Simulacija bez otpora zraka",

417 command = setSimulationState2,

418 variable = SimulationStateIndicator,

419 value = [2], pos = (0,0,self.offsetZ()),

420 scale = 0.05, rolloverSound = None,

421 clickSound = None),

422 DirectRadioButton(parent = self.mainFrame, boxPlacement = 'right',

423 boxImage=img2, image = img, image_pos = (0,0,0.25),

424 image_scale = (7,1,1), relief = None,

425 text = "Simulacija neovisnosti gibanja",

426 command = setSimulationState3,

427 variable = SimulationStateIndicator, value = [3],

428 pos = (0,0,self.offsetZ()), scale = 0.05,

429 rolloverSound = None, clickSound = None)

430 ]

36

431 for btn in self.gui_elem_2:

432 btn.setOthers(self.gui_elem_2)

433 self.gui_elem_3 = DirectLabel(parent = self.mainFrame,

434 text = "Pocetni uvjeti",

435 pos = (0,0,self.offsetZ()), scale = 0.05,

436 textMayChange = 0, frameColor = (0,0,0,0))

437 self.gui_elem_4 = DirectLabel(parent = self.mainFrame,

438 text = "Masa (50 - 150 kg):",

439 pos = (-0.07,0,self.offsetZ()),

440 scale = 0.05, textMayChange = 0,

441 frameColor = (0,0,0,0))

442 self.gui_elem_5 = DirectEntry(parent = self.mainFrame, image = img4,

443 image_pos = (1.885,0,0.25),

444 image_scale = (2,1,1), relief = None,

445 initialText = "97.2", scale = 0.05,

446 pos = (0.156,0,self.pos_z), width = 3.75,

447 command = setSimulationMass,

448 rolloverSound = None, clickSound = None)

449 self.gui_elem_6 = DirectLabel(parent = self.mainFrame,

450 text = "Visina (100 - 1500 m):",

451 pos = (-0.105,0,self.offsetZ()),

452 scale = 0.05, textMayChange = 0,

453 frameColor = (0,0,0,0))

454 self.gui_elem_7 = DirectEntry(parent = self.mainFrame, image = img4,

455 image_pos = (1.885,0,0.25),

456 image_scale = (2,1,1), relief = None,

457 initialText = "1500", scale = 0.05,

458 pos = (0.156,0,self.pos_z), width = 3.75,

459 command = setSimulationHeight,

460 rolloverSound = None, clickSound = None)

461 self.gui_elem_8 = DirectButton(parent = self.mainFrame, image = img,

462 image_pos = (0,0,0.25),

463 image_scale = (7,1,1), relief = None,

464 text = ("Izlaz"),

465 pos = (0,0,self.offsetZ()), scale = 0.05,

466 rolloverSound = None, clickSound = None,

467 command = parent.gotoMain)

468 self.hide()

469

470 # A method for hiding the menu, it also stores possible input data.

471 # Metoda za skrivanje izbornika, pohranjuje i potencijalno unesene podatke.

472

473 def hide(self):

474 setSimulationMass(self.gui_elem_5.get())

475 setSimulationHeight(self.gui_elem_7.get())

476 self.mainFrame.hide()

477

478 # A method for showing the menu.

479 # Metoda za prikazivanje izbornika.

37

480

481 def show(self):

482 self.mainFrame.show()

483

484 # A method used when positioning menu items and/or text.

485 # Metoda koja se koristi prilikom pozicioniranja elemenata izbornika

486 # i/ili teksta.

487

488 def offsetZ(self):

489 self.pos_z -= 0.125

490 return self.pos_z

491

492

493 # A class for creating analysis settings menu.

494 # Klasa za kreiranje izbornika postavki analize.

495

496 class AnalysisSettings(DirectObject):

497 def __init__(self, parent):

498 self.parent = parent

499 self.mainFrame = DirectFrame(frameColor = (1,1,1,1),

500 frameSize = (-0.001,0.001,-0.001,0.001),

501 relief = None)

502 self.pos_z = 0.60

503 self.gui_elem_1 = DirectLabel(parent = self.mainFrame,

504 text = "Vrsta analize",

505 pos = (0,0,self.pos_z), scale = 0.05,

506 textMayChange = 0, frameColor = (0,0,0,0))

507 self.gui_elem_2 = [

508 DirectRadioButton(parent = self.mainFrame, boxPlacement = 'right',

509 boxImage=img2, image = img, image_pos = (0,0,0.25),

510 image_scale = (7,1,1), relief = None,

511 text = "Standardna analiza",

512 variable = AnalysisStateIndicator,

513 value = [1], pos = (0,0,self.offsetZ()),

514 scale = 0.05, rolloverSound = None,

515 clickSound = None),

516 DirectRadioButton(parent = self.mainFrame, boxPlacement = 'right',

517 boxImage=img2, image = img, image_pos = (0,0,0.25),

518 image_scale = (7,1,1), relief = None,

519 text = "Analiza bez otpora zraka",

520 variable = AnalysisStateIndicator, value = [2],

521 pos = (0,0,self.offsetZ()), scale = 0.05,

522 rolloverSound = None, clickSound = None)

523 ]

524 for btn in self.gui_elem_2:

525 btn.setOthers(self.gui_elem_2)

526 self.gui_elem_3 = DirectLabel(parent = self.mainFrame,

527 text = "Pocetni uvjeti",

528 pos = (0,0,self.offsetZ()), scale = 0.05,

38

529 textMayChange = 0, frameColor = (0,0,0,0))

530 self.gui_elem_4 = DirectLabel(parent = self.mainFrame,

531 text = "Masa (0.1 - 1000 kg):",

532 pos = (-0.09,0,self.offsetZ()),

533 scale = 0.05, textMayChange = 0,

534 frameColor = (0,0,0,0))

535 self.gui_elem_5 = DirectEntry(parent = self.mainFrame, image = img4,

536 image_pos = (1.885,0,0.25),

537 image_scale = (2,1,1), relief = None,

538 initialText = "97.2", scale = 0.05,

539 pos = (0.156,0,self.pos_z), width = 3.75,

540 command = setAnalysisMass,

541 rolloverSound = None, clickSound = None)

542 self.gui_elem_6 = DirectLabel(parent = self.mainFrame,

543 text = "Visina (0.1 - 15000 m):",

544 pos = (-0.11,0,self.offsetZ()),

545 scale = 0.05, textMayChange = 0,

546 frameColor = (0,0,0,0))

547 self.gui_elem_7 = DirectEntry(parent = self.mainFrame, image = img4,

548 image_pos = (1.885,0,0.25),

549 image_scale = (2,1,1), relief = None,

550 initialText = "1500", scale = 0.05,

551 pos = (0.156,0,self.pos_z), width = 3.75,

552 command = setAnalysisHeight,

553 rolloverSound = None, clickSound = None)

554 self.gui_elem_8 = DirectLabel(parent = self.mainFrame,

555 text = "Otvaranje padobrana",

556 pos = (0,0,self.offsetZ()), scale = 0.05,

557 textMayChange = 0, frameColor = (0,0,0,0))

558 self.gui_elem_9 = DirectLabel(parent = self.mainFrame,

559 text = "Trenutak otvaranja (>=0 s):",

560 pos = (-0.16,0, self.offsetZ()),

561 scale = 0.05, textMayChange = 0,

562 frameColor = (0,0,0,0))

563 self.gui_elem_10 = DirectEntry(parent = self.mainFrame, image = img4,

564 image_pos = (1.885,0,0.25),

565 image_scale = (2,1,1), relief = None,

566 initialText = "10", scale = 0.05,

567 pos = (0.156, 0,self.pos_z),

568 width = 3.75, command = setTimeOfOpening,

569 rolloverSound = None, clickSound = None)

570 self.gui_elem_11 = DirectButton(parent = self.mainFrame, image = img,

571 image_pos = (0,0,0.25),

572 image_scale = (7,1,1), relief = None,

573 text = ("Izlaz"),

574 pos = (0,0,self.offsetZ()),

575 scale = 0.05, rolloverSound = None,

576 clickSound = None,

577 command = parent.gotoMain)

39

578 self.gui_elem_12 = DirectLabel(parent = self.mainFrame,

579 text = ("\nNapomena: ukoliko uopce ne " +

580 "zelite aktivaciju padobrana\npostavite"+

581 " trenutak otvaranja na 0 s\n\n" +

582 "Upozorenje: za jako male/velike mase " +

583 "standardna analiza\nne daje nuzno " +

584 "fizikalno ispravna rjesenja!"),

585 pos = (0,0,self.offsetZ()), scale = 0.05,

586 textMayChange = 0,

587 frameColor = (0,0,0,0))

588 self.hide()

589

590 # A method for hiding the menu, it also stores possible input data.

591 # Metoda za skrivanje izbornika, pohranjuje i potencijalno unesene podatke.

592

593 def hide(self):

594 setAnalysisMass(self.gui_elem_5.get())

595 setAnalysisHeight(self.gui_elem_7.get())

596 setTimeOfOpening(self.gui_elem_10.get())

597 self.mainFrame.hide()

598

599 # A method for showing the menu.

600 # Metoda za prikazivanje izbornika.

601

602 def show(self):

603 self.mainFrame.show()

604

605 # A method used when positioning menu items and/or text.

606 # Metoda koja se koristi prilikom pozicioniranja elemenata izbornika

607 # i/ili teksta.

608

609 def offsetZ(self):

610 self.pos_z -= 0.125

611 return self.pos_z

612

613

614 # A class for creating visual settings menu.

615 # Klasa za kreiranje izbornika postavki prikaza.

616

617 class VisualSettings(DirectObject):

618 def __init__(self, parent):

619 self.parent = parent

620 self.mainFrame = DirectFrame(frameColor = (1,1,1,1),

621 frameSize = (-0.001,0.001,-0.001,0.001),

622 relief = None)

623 self.pos_z = 0.925

624 self.firstRun = True

625 self.cWidth = ""

626 self.cHeight = ""

40

627 self.gui_elem_1 = DirectLabel(parent = self.mainFrame, text = "Prikaz",

628 pos = (0,0,self.pos_z), scale = 0.05,

629 textMayChange = 0, frameColor = (0,0,0,0))

630 self.gui_elem_2 = DirectCheckButton(parent = self.mainFrame,

631 boxPlacement = 'right',

632 boxImage = img3, boxImageScale = 1,

633 boxRelief = None, image = img,

634 image_pos = (0,0,0.25),

635 image_scale = (7,1,1),

636 relief = None,

637 text = ("Prikaz broja slicica" +

638 " u sekundi"),

639 command = setFPScounter,

640 indicatorValue = 0,

641 pos = (0,0,self.offsetZ()),

642 scale = 0.05, rolloverSound = None,

643 clickSound = None)

644 self.gui_elem_3 = DirectCheckButton(parent = self.mainFrame,

645 boxPlacement = 'right',

646 boxImage = img3, boxImageScale = 1,

647 boxRelief = None, image = img,

648 image_pos = (0,0,0.25),

649 image_scale = (7,1,1),

650 relief = None, text = ("Prikaz u" +

651 " cijelom zaslonu"),

652 command = self.changeScreen,

653 indicatorValue = 0 ,

654 pos = (0,0,self.offsetZ()),

655 scale = 0.05, rolloverSound = None,

656 clickSound = None)

657 self.gui_elem_4 = DirectLabel(parent = self.mainFrame,

658 text = "Razlucivost",

659 pos = (0,0,self.offsetZ()),

660 scale = 0.05, textMayChange = 0,

661 frameColor = (0,0,0,0))

662 self.gui_elem_5 = DirectButton(parent = self.mainFrame, image = img,

663 image_pos = (0,0,0.25),

664 image_scale = (3.47005,1,1),

665 relief = None, text = "800 x 600",

666 command = self.changeResolution,

667 extraArgs = [800,600],

668 pos = (-0.17655,0,self.offsetZ()),

669 scale = 0.05, rolloverSound = None,

670 clickSound = None)

671 self.gui_elem_6 = DirectButton(parent = self.mainFrame, image = img,

672 image_pos = (0,0,0.25),

673 image_scale = (3.47005,1,1),

674 relief = None, text = "1024 x 768",

675 command = self.changeResolution,

41

676 extraArgs = [1024,768],

677 pos = (0.17655,0,self.pos_z),

678 scale = 0.05, rolloverSound = None,

679 clickSound = None)

680 self.gui_elem_7 = DirectButton(parent = self.mainFrame, image = img,

681 image_pos = (0,0,0.25),

682 image_scale = (3.47005,1,1),

683 relief = None, text = "1280 x 720",

684 command = self.changeResolution,

685 extraArgs = [1280,720],

686 pos = (-0.17655,0,self.offsetZ()),

687 scale = 0.05, rolloverSound = None,

688 clickSound = None)

689 self.gui_elem_8 = DirectButton(parent = self.mainFrame, image = img,

690 image_pos = (0,0,0.25),

691 image_scale = (3.47005,1,1),

692 relief = None, text = "1280 x 800",

693 command = self.changeResolution,

694 extraArgs = [1280,800],

695 pos = (0.17655,0,self.pos_z),

696 scale = 0.05, rolloverSound = None,

697 clickSound = None)

698 self.gui_elem_9 = DirectButton(parent = self.mainFrame, image = img,

699 image_pos = (0,0,0.25),

700 image_scale = (3.47005,1,1),

701 relief = None, text = "1280 x 960",

702 command = self.changeResolution,

703 extraArgs = [1280,960],

704 pos = (-0.17655,0,self.offsetZ()),

705 scale = 0.05, rolloverSound = None,

706 clickSound = None)

707 self.gui_elem_10 = DirectButton(parent = self.mainFrame, image = img,

708 image_pos = (0,0,0.25),

709 image_scale = (3.47005,1,1),

710 relief = None, text = "1440 x 900",

711 command = self.changeResolution,

712 extraArgs = [1440,900],

713 pos = (0.17655,0,self.pos_z),

714 scale = 0.05, rolloverSound = None,

715 clickSound = None)

716 self.gui_elem_11 = DirectButton(parent = self.mainFrame, image = img,

717 image_pos = (0,0,0.25),

718 image_scale = (3.47005,1,1),

719 relief = None, text = "1920 x 1080",

720 command = self.changeResolution,

721 extraArgs = [1920,1080],

722 pos = (-0.17655,0,self.offsetZ()),

723 scale = 0.05, rolloverSound = None,

724 clickSound = None)

42

725 self.gui_elem_12 = DirectButton(parent = self.mainFrame, image = img,

726 image_pos = (0,0,0.25),

727 image_scale = (3.47005,1,1),

728 relief = None, text = "1920 x 1200",

729 command = self.changeResolution,

730 extraArgs = [1920,1200],

731 pos = (0.17655,0,self.pos_z),

732 scale = 0.05, rolloverSound = None,

733 clickSound = None)

734 self.gui_elem_13 = DirectLabel(parent = self.mainFrame, text = "",

735 pos = (0,0,self.offsetZ()), scale = 0.05,

736 textMayChange = 1,

737 frameColor = (0,0,0,0))

738 self.gui_elem_14 = DirectLabel(parent = self.mainFrame,

739 text = "Proizvoljna razlucivost",

740 pos = (0,0,self.offsetZ()), scale = 0.05,

741 textMayChange = 0,

742 frameColor = (0,0,0,0))

743 self.gui_elem_15 = DirectLabel(parent = self.mainFrame,

744 text = "Sirina zaslona (>=320):",

745 pos = (-0.11,0,self.offsetZ()),

746 scale = 0.05, textMayChange = 0,

747 frameColor = (0,0,0,0))

748 self.gui_elem_16 = DirectEntry(parent = self.mainFrame, image = img4,

749 image_pos = (1.885,0,0.25),

750 image_scale = (2,1,1), relief = None,

751 initialText = "800", scale = 0.05,

752 pos = (0.156,0,self.pos_z), width = 3.75,

753 command = self.storeCustomWidth,

754 rolloverSound = None, clickSound = None)

755 self.gui_elem_17 = DirectLabel(parent = self.mainFrame,

756 text = "Visina zaslona (>=240):",

757 pos = (-0.115,0,self.offsetZ()),

758 scale = 0.05, textMayChange = 0,

759 frameColor = (0,0,0,0))

760 self.gui_elem_18 = DirectEntry(parent = self.mainFrame, image = img4,

761 image_pos = (1.885,0,0.25),

762 image_scale = (2,1,1), relief = None,

763 initialText = "600", scale = 0.05,

764 pos = (0.156,0,self.pos_z), width = 3.75,

765 command = self.storeCustomHeight,

766 rolloverSound = None, clickSound = None)

767 self.gui_elem_19 = DirectButton(parent = self.mainFrame, image = img,

768 image_pos = (0,0,0.25),

769 image_scale = (7,1,1), relief = None,

770 text = ("Postavi proizvoljnu" +

771 " razlucivost"),

772 pos = (0,0,self.offsetZ()),

773 scale = 0.05, rolloverSound = None,

43

774 clickSound = None,

775 command = self.setCustomResolution)

776 self.gui_elem_20 = DirectButton(parent = self.mainFrame, image = img,

777 image_pos = (0,0,0.25),

778 image_scale = (7,1,1), relief = None,

779 text = ("Izlaz"),

780 pos = (0,0,self.offsetZ()),

781 scale = 0.05, rolloverSound = None,

782 clickSound = None,

783 command = parent.gotoMain)

784 self.gui_elem_21 = DirectLabel(parent = self.mainFrame, text = ("Napo" +

785 "mena: ukoliko postavite pogresnu " +

786 "razlucivost pritisnite F1"),

787 pos = (0,0,self.offsetZ()), scale = 0.05,

788 textMayChange = 0,

789 frameColor = (0,0,0,0))

790 self.hide()

791

792 # A method for hiding the menu, it also stores possible input data and does

793 # the necessary task and keyboard event removal.

794 # Metoda za skrivanje izbornika, pohranjuje i potencijalno unesene podatke te

795 # uklanja postojece zadatke i dogadaje.

796

797 def hide(self):

798 taskMgr.removeTasksMatching("checkres")

799 self.accept("f1",self.unbindKey)

800 self.storeCustomWidth(self.gui_elem_16.get())

801 self.storeCustomHeight(self.gui_elem_18.get())

802 if (self.firstRun == False):

803 setCustomWidth(base.win.getXSize())

804 setCustomHeight(base.win.getYSize())

805 self.saveIntoFile()

806 else:

807 self.saveIntoFile()

808 self.firstRun = False

809 self.mainFrame.hide()

810

811 # A method for showing the menu.

812 # Metoda za prikazivanje izbornika.

813

814 def show(self):

815 taskMgr.add(self.checkResolution,"checkres")

816 self.accept("f1", self.revertScreenSettings)

817 if (screenStateFull == True):

818 self.gui_elem_3['indicatorValue'] = 1

819 else:

820 self.gui_elem_3['indicatorValue'] = 0

821 self.mainFrame.show()

822

44

823 # A method for showing the current resolution. It is used entire time the user

824 # is in menu because the resolution might be changed by dragging the program

825 # window with mouse.

826 # Metoda za prikazivanje trenutacne razlucivosti. Koristi se dok se korisnik

827 # nalazi u izborniku buduci da bi korisnik mogao promijeniti razlucivost

828 # razvlacenjem prozora programa pomocu misa.

829

830 def checkResolution(self,task):

831 self.gui_elem_13['text'] = ("Trenutacna razlucivost: "

832 +str(base.win.getXSize())+" x "

833 +str(base.win.getYSize()))

834 return task.cont

835

836 # A method for changing display from full screen to windowed and vice versa.

837 # Metoda za mijenjanje prikaza iz punog zaslona u prozor i obrnuto.

838

839 def changeScreen(self,state):

840 w.flipScreenState()

841

842 # A method for setting the resolution.

843 # Metoda za postavljanje razlucivosti.

844

845 def changeResolution(self,scrWidth,scrHeight):

846 w.setScreenResolution(scrWidth,scrHeight)

847 self.gui_elem_13['text'] = ("Trenutacna razlucivost: "

848 +str(screenWidth)+" x "+str(screenHeight))

849

850 # A "fail-safe" method for reverting to default resolution if the user chooses

851 # an incorrect one.

852 # "Sigurnosna" metoda koja vraca razlucivost na pocetnu vrijednost ukoliko

853 # korisnik odabere neodgovarajucu razlucivost.

854

855 def revertScreenSettings(self):

856 global screenStateFull

857 screenStateFull = False

858 w.setScreenResolution(800, 600)

859 self.gui_elem_3['indicatorValue'] = 0

860 self.gui_elem_13['text'] = ("Trenutacna razlucivost: "

861 +str(screenWidth)+" x "+str(screenHeight))

862

863 # A method which is used when we no longer want to accept keyboard input for

864 # a specific command.

865 # Metoda koja se koristi kada vise ne zelimo primati unos sa tipkovnice za

866 # specificnu naredbu.

867

868 def unbindKey(self):

869 pass

870

871 # Following methods are used for setting the resolution.

45

872 # Naredne metode se koriste za postavljanje razlucivosti.

873

874 def storeCustomWidth(self,incwidth):

875 self.cWidth = incwidth

876

877 def storeCustomHeight(self,incheight):

878 self.cHeight = incheight

879

880 def setCustomResolution(self):

881 self.storeCustomWidth(self.gui_elem_16.get())

882 self.storeCustomHeight(self.gui_elem_18.get())

883 setCustomWidth(self.cWidth)

884 setCustomHeight(self.cHeight)

885 self.cWidth = screenWidth

886 self.cHeight = screenHeight

887 w.setScreenResolution(screenWidth,screenHeight)

888 self.gui_elem_13['text'] = ("Trenutacna razlucivost: "

889 +str(screenWidth)+" x "+str(screenHeight))

890

891 # A method for saving chosen resolution. Without this method (and the one which

892 # reads the data) program would always start with the same resolution.

893 # Metoda za pohranjivanje postavljene razlucivosti. Bez ove metode (te one koja

894 # ucitava podatke) program bi uvijek zapocinjao sa istom razlucivosti.

895

896 def saveIntoFile(self):

897 try:

898 self.settingsFile = open("postavke.txt", "w")

899 except:

900 pass

901 else:

902 self.settingsFile.write("Sirina prikaza:\n"+str(screenWidth)

903 +"\nVisina prikaza:\n"+str(screenHeight)

904 +"\nPrikaz u cijelom zaslonu (1 za ukljuc" +

905 "eno, 0 za iskljuceno):\n")

906 if (screenStateFull == False):

907 self.settingsFile.write("0")

908 else:

909 self.settingsFile.write("1")

910 self.settingsFile.close()

911

912 # A method used when positioning menu items and/or text.

913 # Metoda koja se koristi prilikom pozicioniranja elemenata izbornika

914 # i/ili teksta.

915

916 def offsetZ(self):

917 self.pos_z -= 0.125

918 return self.pos_z

919

920

46

921 # A class for creating info menu.

922 # Klasa za kreiranje izbornika sa informacijama.

923

924 class Info(DirectObject):

925 def __init__(self, parent):

926 self.parent = parent

927 self.mainFrame = DirectFrame(frameColor=(1,1,1,1),

928 frameSize = (-0.001,0.001,-0.001,0.001),

929 relief = None)

930 self.pos_z = 0.45

931 self.gui_elem_1 = DirectLabel(parent = self.mainFrame, text = "Info",

932 pos = (0,0,self.pos_z), scale = 0.05,

933 textMayChange = 0, frameColor = (0,0,0,0))

934 self.gui_elem_2 = DirectLabel(parent = self.mainFrame,

935 text = ("Padobranac 1.0\n\nGoran Jambre" +

936 "sic, 2011.\n\nOvaj program je dan na " +

937 "koristenje pod GNU General Public lice" +

938 "ncom.\nLicenca je dostupna " +

939 "na: http://www.gnu.org/licenses/" +

940 "\n te u license.txt dokumentu unutar " +

941 "direktorija programa." +

942 "\n\nKorisnicke upute mogu se pronaci" +

943 " u upute.txt\ndokumentu unutar" +

944 " direktorija programa."),

945 pos = (0,0,self.offsetZ()), scale = 0.05,

946 textMayChange = 0, frameColor = (0,0,0,0))

947 self.gui_elem_3 = DirectButton(parent = self.mainFrame, image = img,

948 image_pos = (0,0,0.25),

949 image_scale = (5,1,1), relief = None,

950 text = ("Izlaz"),

951 pos = (0,0,self.offsetZ()-0.5),

952 scale = 0.05, rolloverSound = None,

953 clickSound = None,

954 command = parent.gotoMain)

955 self.hide()

956

957 # A method for hiding the menu.

958 # Metoda za skrivanje izbornika.

959

960 def hide(self):

961 self.mainFrame.hide()

962

963 # A method for showing the menu.

964 # Metoda za prikazivanje izbornika.

965

966 def show(self):

967 self.mainFrame.show()

968

969 # A method used when positioning menu items and/or text.

47

970 # Metoda koja se koristi prilikom pozicioniranja elemenata izbornika

971 # i/ili teksta.

972

973 def offsetZ(self):

974 self.pos_z -= 0.125

975 return self.pos_z

976

977

978 # A class which takes care of everything - from setting visual scenes to

979 # physics. All the necessary models and menus are loaded in the beginning.

980 # Klasa koja povezuje sve - od grafickog prikaza do fizikalnih proracuna.

981 # Svi potrebni modeli i izbornici su ucitani tijekom pocetne inicijalizacije.

982

983 class Simulation(DirectObject):

984 def __init__(self):

985 base.setBackgroundColor(0.77, 0.83, 0.98)

986 self.preloadSettings()

987 self.setScreenResolution(screenWidth, screenHeight)

988 self.scene1 = render.attachNewNode(PandaNode("branch1"))

989 self.scene2 = render.attachNewNode(PandaNode("branch2"))

990 self.scene3 = render.attachNewNode(PandaNode("branch3"))

991 self.scene1.hide()

992 self.scene2.hide()

993 self.scene3.hide()

994 self.setSimulationVariant()

995 self.initializeOnce()

996 self.initializePhysicsAgain()

997 self.createEnvironment()

998 self.createEnvironment2()

999 self.initializeAgain()

1000 self.disableControls()

1001 self.imageObject = OnscreenImage(image = 'menu/background2.png',

1002 pos = (0, 0, 0), scale = (5,1,3))

1003 self.choice1 = MainMenu(self)

1004 self.choice2 = GraphicalAnalysis(self)

1005 self.choice3 = SimulationSettings(self)

1006 self.choice4 = AnalysisSettings(self)

1007 self.choice5 = VisualSettings(self)

1008 self.choice6 = Info(self)

1009

1010 # A method for starting the simulation. Necessary rendering branches are

1011 # shown wile others are hidden and the models are repositioned. Everything

1012 # is set in place for calculating physical model.

1013 # Metoda za pokretanje simulacije. Prikazuju se samo nuzni modeli dok su ostali

1014 # skriveni. Postavlja se sve nuzno za fizikalne proracune.

1015

1016 def startSim(self):

1017 self.setSimulationVariant()

1018 self.choice1.hide()

48

1019 self.imageObject.hide()

1020 self.initializePhysicsAgain()

1021 self.initializeAgain()

1022 self.resetWorldObjects()

1023 self.resetWorld()

1024 self.enableControls()

1025 if (self.TwoSkydivers == True):

1026 self.showBranch23()

1027 else:

1028 self.showBranch13()

1029

1030 # Following methods are used for setting the resolution.

1031 # Naredne metode se koriste za postavljanje razlucivosti.

1032

1033 def preloadSettings(self):

1034 try:

1035 self.settingsFile = open("postavke.txt", "r")

1036 except:

1037 pass

1038 else:

1039 try:

1040 global screenStateFull

1041 self.settingsInput = self.settingsFile.readlines()

1042 setCustomWidth(self.settingsInput[1])

1043 setCustomHeight(self.settingsInput[3])

1044 if (int(self.settingsInput[5]) == 1):

1045 screenStateFull = True

1046 self.settingsFile.close()

1047 except:

1048 self.settingsFile.close()

1049

1050 def toggleFullScreen(self, width, height, posX, posY, full):

1051 self.winProps = WindowProperties()

1052 self.winProps.setOrigin(posX, posY)

1053 self.winProps.setSize(width, height)

1054 self.winProps.setFullscreen(full)

1055 base.win.requestProperties(self.winProps)

1056

1057 def flipScreenState(self):

1058 global screenStateFull

1059 if (screenStateFull == True):

1060 self.toggleFullScreen(screenWidth, screenHeight, 50, 50, False)

1061 screenStateFull = False

1062 else:

1063 self.toggleFullScreen(screenWidth, screenHeight, 0, 0, True)

1064 screenStateFull = True

1065

1066 def setScreenResolution(self,scrWidth,scrHeight):

1067 global screenWidth

49

1068 global screenHeight

1069 if (screenStateFull == False):

1070 screenWidth = scrWidth

1071 screenHeight = scrHeight

1072 self.toggleFullScreen(screenWidth, screenHeight, 50, 50, False)

1073 else:

1074 screenWidth = scrWidth

1075 screenHeight = scrHeight

1076 self.toggleFullScreen(screenWidth, screenHeight, 0, 0, True)

1077

1078 # Following methods are used for displaying menus and certain parts of

1079 # simulation.

1080 # Naredne metode se koriste za prikazivanje izbornika i odredenih dijelova

1081 # simulacije.

1082

1083 def options1(self):

1084 self.choice1.hide()

1085 self.useSimulationData = False

1086 self.choice2.show()

1087

1088 def options2(self):

1089 self.choice1.hide()

1090 self.choice3.show()

1091

1092 def options3(self):

1093 self.choice1.hide()

1094 self.choice4.show()

1095

1096 def options4(self):

1097 self.choice1.hide()

1098 self.choice5.show()

1099

1100 def options5(self):

1101 self.choice1.hide()

1102 self.choice6.show()

1103

1104 def exit(self):

1105 exit()

1106

1107 def gotoMain(self):

1108 self.choice2.hide()

1109 self.choice3.hide()

1110 self.choice4.hide()

1111 self.choice5.hide()

1112 self.choice6.hide()

1113 self.choice1.show()

1114

1115 def showBranch13(self):

1116 self.scene1.show()

50

1117 self.scene3.show()

1118

1119 def showBranch23(self):

1120 self.scene2.show()

1121 self.scene3.show()

1122

1123 def hideBranch13(self):

1124 self.scene1.hide()

1125 self.scene3.hide()

1126

1127 def hideBranch23(self):

1128 self.scene2.hide()

1129 self.scene3.hide()

1130

1131 # Following methods are used for setting various aspects of simulation.

1132 # Naredne metode se koriste za postavljanje razlicitih aspekata simulacije.

1133

1134 def endSimulation(self):

1135 self.killAllTasks()

1136 self.clearEntireReadout()

1137 if (showFPScounter == True):

1138 base.setFrameRateMeter(False)

1139 base.win.removeDisplayRegion(self.region)

1140 if (self.TwoSkydivers == True):

1141 base.win.removeDisplayRegion(self.region2)

1142 if (self.TwoSkydivers == True):

1143 self.hideBranch23()

1144 else:

1145 self.hideBranch13()

1146 self.disableControls()

1147 self.imageObject.show()

1148 if (self.useSimulationData == True):

1149 self.choice2.show()

1150 else:

1151 self.choice1.show()

1152

1153 def killAllTasks(self):

1154 taskMgr.removeTasksMatching("isee")

1155 taskMgr.removeTasksMatching("isee2")

1156 taskMgr.removeTasksMatching("wind1")

1157 taskMgr.removeTasksMatching("wind2")

1158 taskMgr.removeTasksMatching("noDrag")

1159 taskMgr.removeTasksMatching("jumpAO")

1160 taskMgr.removeTasksMatching("jumpAO2")

1161 taskMgr.removeTasksMatching("jumpBO")

1162 taskMgr.removeTasksMatching("jumpBO2")

1163

1164 def initializeOnce(self):

1165 self.height = SimulationHeight

51

1166 self.g = 9.807

1167 self.worldsize = 1024

1168 self.correction = 10.7

1169 self.txt_pos1 = -0.10

1170 self.txt_pos2 = -0.35

1171 self.txt_pos3 = -0.10

1172 self.txt_pos4 = 0.40

1173 self.txt_pos5 = 0.15

1174 self.txt_pos6 = 0.40

1175 self.setupCD()

1176 self.lightsON()

1177 self.createEnvironmentCommon()

1178

1179 def clearMemory(self):

1180 try:

1181 del self.lst_time[:]

1182 del self.lst_acceleration[:]

1183 del self.lst_velocity[:]

1184 del self.lst_position[:]

1185 gc.collect()

1186 except:

1187 pass

1188 try:

1189 del self.g_a

1190 del self.g_v

1191 del self.g_p

1192 del self.graph_acc

1193 del self.graph_vel

1194 del self.graph_pos

1195 gc.collect()

1196 except:

1197 pass

1198 gc.collect()

1199

1200 def initializePhysicsAgain(self):

1201 self.clearMemory()

1202 self.lst_time = []

1203 self.lst_acceleration = []

1204 self.lst_velocity = []

1205 self.lst_position = []

1206 self.a = -self.g

1207 self.dt = 0.001

1208 self.m = SimulationMass

1209 self.k = 1.0

1210 self.vt = 0.0

1211 self.fd = 0.0

1212 self.aend = 0.0

1213 self.a1 = 43.8

1214 self.b0 = 0.5

52

1215 self.b1 = 0.1

1216 self.h = 1.78

1217 self.l = 8.96

1218 self.t0 = TimeOfOpening

1219 self.beta1 = 0.15

1220 self.sa0 = 0.0

1221 self.sb0 = 0.0

1222 self.tk = 9999

1223 self.v = 0.0

1224 self.y = SimulationHeight

1225 self.t = 0.0

1226 self.t1 = self.t0 + 0.5

1227 self.t2 = self.t0 + 1.5

1228 self.t3 = self.t0 + 3.2

1229 self.sa0 = self.alfa0()

1230 self.sb0 = self.beta0()

1231

1232 def setSimulationVariant(self):

1233 self.TwoSkydivers = TwoSkydiversOn

1234 self.air_drag = DragOff

1235

1236 def initializeAgain(self):

1237 self.useSimulationData = False

1238 self.height = SimulationHeight

1239 self.opened_chute = False

1240 self.jumpOver = False

1241 self.wsx = 0

1242 self.wsy = 0

1243 self.ws = 0

1244 self.flag = 5

1245 self.dir = ""

1246 self.angleDegrees = 0

1247 self.angleDegrees2 = 0

1248 self.angleDegrees2c = [0,0]

1249 self.angleDegrees2c2 = [0,0]

1250 self.active_cam = 0

1251 self.cam_dist = 40

1252 self.cam_dists = [40,40]

1253 self.cam_height = 0.5

1254 self.cam_heights = [0.5,0.5]

1255 self.text_color = 1

1256 self.offset = 0

1257 self.enableControls()

1258

1259 def catchVariables(self):

1260 try:

1261 self.wind_txt

1262 except:

1263 pass

53

1264 else:

1265 self.wind_txt.destroy()

1266 try:

1267 self.debrief

1268 except:

1269 pass

1270 else:

1271 self.debrief.destroy()

1272

1273 def catchWind(self):

1274 try:

1275 self.wind_txt

1276 except:

1277 pass

1278 else:

1279 self.wind_txt.destroy()

1280

1281 def resetWorld(self):

1282 self.catchVariables()

1283 self.calculateVT()

1284 if (showFPScounter == True):

1285 base.setFrameRateMeter(True)

1286 if (self.TwoSkydivers == True):

1287 self.text_color = 0

1288 self.offset = 1.5

1289 self.data_l = self.addInstructionsBL(self.txt_pos4,("Visina: " +

1290 '%.2f' %(self.y)+

1291 " m\n\nPritisnite space za" +

1292 " pocetak skoka\nte kasnije" +

1293 " za otvaranje padobrana"))

1294 self.wind_txt = self.addInstructionsBL(self.txt_pos5,("Vjetar " +

1295 "djeluje na plavog " +

1296 "padobranca tijekom skoka"))

1297 self.data_r = self.addInstructionsBC(self.txt_pos6,("Visina: " +

1298 '%.2f' %(self.y) +

1299 " m\n\n\n\n\nVjetar ne " +

1300 "djeluje na crvenog " +

1301 "padobranca"))

1302 base.cam.node().getDisplayRegion(0).setActive(0)

1303 self.cameras = [

1304 base.makeCamera(base.win),

1305 base.makeCamera(base.win)

1306 ]

1307 self.makeRegion2(self.cameras[0], Vec4(0, 0.5, 0, 1),

1308 Vec4(0.77, 0.83, 0.98, 1))

1309 self.makeRegion(self.cameras[1], Vec4(0.5, 1, 0, 1),

1310 Vec4(0.77, 0.83, 0.98, 1))

1311 self.cameras[0].reparentTo(self.skydiver3)

1312 self.cameras[0].setY(self.cameras[0], -40)

54

1313 self.cameras[0].setZ(self.cameras[0], 0.5)

1314 self.cameras[1].reparentTo(self.skydiver2)

1315 self.cameras[1].setY(self.cameras[1], -40)

1316 self.cameras[1].setZ(self.cameras[1], 0.5)

1317 self.accept("space", self.setSpaceKey2)

1318 taskMgr.add(self.isee2,"isee2")

1319 else:

1320 self.text_color = 1

1321 self.offset = 0

1322 self.wind_txt = self.addInstructionsTL(self.txt_pos2,"")

1323 self.data_l = self.addInstructionsTL(self.txt_pos1,("Visina: "

1324 +'%.2f' %(self.y) +

1325 " m\n\nPritisnite space za" +

1326 " pocetak skoka\nte kasnije" +

1327 " za otvaranje padobrana"))

1328 base.cam.node().getDisplayRegion(0).setActive(0)

1329 self.camera = base.makeCamera(base.win)

1330 self.makeRegion(self.camera, Vec4(0, 1, 0, 1),

1331 Vec4(0.77, 0.83, 0.98, 1))

1332 self.camera.reparentTo(self.skydiver)

1333 self.camera.setY(self.camera, -40)

1334 self.camera.setZ(self.camera, 0.5)

1335 self.accept("space", self.setSpaceKey)

1336 taskMgr.add(self.wind,"wind1")

1337 taskMgr.add(self.isee,"isee")

1338 self.help_txt = self.addInstructionsTR(self.txt_pos3,"F1:upute")

1339

1340 def enableControls(self):

1341 base.disableMouse()

1342 self.keyMap = {"arrow_left" : False, "arrow_up" : False,

1343 "arrow_down" : False, "arrow_right" : False,

1344 "mouse1" : False, "mouse3" : False,

1345 "page_down" : False, "page_up" : False}

1346 self.accept("mouse1", self.setKey, ["mouse1", True])

1347 self.accept("mouse3", self.setKey, ["mouse3", True])

1348 self.accept("mouse1-up", self.setKey, ["mouse1", False])

1349 self.accept("mouse3-up", self.setKey, ["mouse3", False])

1350 self.accept("arrow_left", self.setKey, ["arrow_left", True])

1351 self.accept("arrow_up", self.setKey, ["arrow_up", True])

1352 self.accept("arrow_left-up", self.setKey, ["arrow_left", False])

1353 self.accept("arrow_up-up", self.setKey, ["arrow_up", False])

1354 self.accept("arrow_down", self.setKey, ["arrow_down", True])

1355 self.accept("arrow_right", self.setKey, ["arrow_right", True])

1356 self.accept("arrow_down-up", self.setKey, ["arrow_down", False])

1357 self.accept("arrow_right-up", self.setKey, ["arrow_right", False])

1358 self.accept("page_down", self.setKey, ["page_down", True])

1359 self.accept("page_up", self.setKey, ["page_up", True])

1360 self.accept("page_down-up", self.setKey, ["page_down", False])

1361 self.accept("page_up-up", self.setKey, ["page_up", False])

55

1362 self.accept("escape", self.endSimulation)

1363 self.accept("l", self.activeL)

1364 self.accept("r", self.activeR)

1365 self.accept("o", self.cameraReset)

1366 self.accept("f1", self.updateReadoutHelp)

1367 self.accept("1", self.windSW)

1368 self.accept("2", self.windS)

1369 self.accept("3", self.windSE)

1370 self.accept("4", self.windW)

1371 self.accept("5", self.windNone)

1372 self.accept("6", self.windE)

1373 self.accept("7", self.windNW)

1374 self.accept("8", self.windN)

1375 self.accept("9", self.windNE)

1376 self.accept("+", self.windSpeedIncr)

1377 self.accept("-", self.windSpeedDecr)

1378

1379 def disableControls(self):

1380 base.enableMouse()

1381 self.accept("mouse1", self.setSpaceKeyYetAgain)

1382 self.accept("mouse3", self.setSpaceKeyYetAgain)

1383 self.accept("mouse1-up", self.setSpaceKeyYetAgain)

1384 self.accept("mouse3-up", self.setSpaceKeyYetAgain)

1385 self.accept("arrow_left", self.setSpaceKeyYetAgain)

1386 self.accept("arrow_up", self.setSpaceKeyYetAgain)

1387 self.accept("arrow_left-up", self.setSpaceKeyYetAgain)

1388 self.accept("arrow_up-up", self.setSpaceKeyYetAgain)

1389 self.accept("arrow_down", self.setSpaceKeyYetAgain)

1390 self.accept("arrow_right", self.setSpaceKeyYetAgain)

1391 self.accept("arrow_down-up", self.setSpaceKeyYetAgain)

1392 self.accept("arrow_right-up", self.setSpaceKeyYetAgain)

1393 self.accept("page_down", self.setSpaceKeyYetAgain)

1394 self.accept("page_up", self.setSpaceKeyYetAgain)

1395 self.accept("page_down-up", self.setSpaceKeyYetAgain)

1396 self.accept("page_up-up", self.setSpaceKeyYetAgain)

1397 self.accept("escape", sys.exit)

1398 self.accept("l", self.setSpaceKeyYetAgain)

1399 self.accept("r", self.setSpaceKeyYetAgain)

1400 self.accept("o", self.setSpaceKeyYetAgain)

1401 self.accept("f1", self.setSpaceKeyYetAgain)

1402 self.accept("1", self.setSpaceKeyYetAgain)

1403 self.accept("2", self.setSpaceKeyYetAgain)

1404 self.accept("3", self.setSpaceKeyYetAgain)

1405 self.accept("4", self.setSpaceKeyYetAgain)

1406 self.accept("5", self.setSpaceKeyYetAgain)

1407 self.accept("6", self.setSpaceKeyYetAgain)

1408 self.accept("7", self.setSpaceKeyYetAgain)

1409 self.accept("8", self.setSpaceKeyYetAgain)

1410 self.accept("9", self.setSpaceKeyYetAgain)

56

1411 self.accept("+", self.setSpaceKeyYetAgain)

1412 self.accept("-", self.setSpaceKeyYetAgain)

1413

1414 # Following methods are used for information display during simulation.

1415 # Naredne metode se koriste za prikaz informacija tijekom simulacije.

1416

1417 def addInstructionsTL(self, pos, msg):

1418 return OnscreenText(text=msg, style=1, fg=(self.text_color,

1419 self.text_color,self.text_color,1), pos=(0.15,pos),

1420 parent = base.a2dTopLeft, align=TextNode.ALeft,

1421 scale = 0.05)

1422

1423 def addInstructionsTR(self, pos, msg):

1424 return OnscreenText(text=msg, style=1, fg=(self.text_color,

1425 self.text_color,self.text_color,1), pos=(-0.8,pos),

1426 parent = base.a2dTopRight, align=TextNode.ALeft,

1427 scale = 0.05)

1428

1429 def addInstructionsBL(self, pos, msg):

1430 return OnscreenText(text=msg, style=1, fg=(self.text_color,

1431 self.text_color,self.text_color,1), pos=(0.15,pos),

1432 parent = base.a2dBottomLeft, align=TextNode.ALeft,

1433 scale = 0.05)

1434

1435 def addInstructionsBC(self, pos, msg):

1436 return OnscreenText(text=msg, style=1, fg=(self.text_color,

1437 self.text_color,self.text_color,1), pos=(0.15,pos),

1438 parent = base.a2dBottomCenter, align=TextNode.ALeft,

1439 scale = 0.05)

1440

1441 def updateReadoutHelp(self):

1442 self.accept("f1", self.updateReadoutHelpClear)

1443 self.help_txt.destroy()

1444 if (self.TwoSkydivers == True):

1445 self.help_txt = self.addInstructionsTR(self.txt_pos3,

1446 ("Esc: izlaz\n\nStrelice: rotacija kamere\n\nPa" +

1447 "ge-up, page-down/lijeva\ni desna tipka misa: " +

1448 "zoom\n\nO: resetiranje kamere\n\nR:"+

1449 " odabir desne kamere\n\nL: odabir lijeve " +

1450 "kamere\n\n+,-: brzina vjetra\n\n1-9: smjer vje" +

1451 "tra, 5 za prekid\n\nPritisnite F1 za " +

1452 "zatvaranje uputa"))

1453 else:

1454 self.help_txt = self.addInstructionsTR(self.txt_pos3,

1455 ("Esc: izlaz\n\nStrelice: rotacija kamere\n\nPa" +

1456 "ge-up, page-down/lijeva\ni desna tipka misa: " +

1457 "zoom\n\nO: resetiranje kamere\n\n+,-: brzina " +

1458 "vjetra\n\n1-9: smjer vjetra, 5 za prekid\n\nPri" +

1459 "tisnite F1 za zatvaranje uputa"))

57

1460

1461 def updateReadoutHelpClear(self):

1462 self.accept("f1", self.updateReadoutHelp)

1463 self.help_txt.destroy()

1464 self.help_txt = self.addInstructionsTR(self.txt_pos3,"F1:upute")

1465

1466 def updateReadout(self):

1467 self.data_l.destroy()

1468 if (self.TwoSkydivers == True):

1469 self.data_l = self.addInstructionsBL(self.txt_pos4,

1470 ("Visina: "+'%.2f' %(self.y)+" m\nBrzina: " +

1471 '%.2f' %(self.v)+" m/s\nAkceleracija: " +

1472 '%.2f' %(self.a)+" m/(s*s)\nSila na padobranca: " +

1473 '%.2f' %(self.m * self.a)+" N\nVrijeme: " +

1474 '%.2f' %(self.t)+" s"))

1475 self.data_r.destroy()

1476 self.data_r = self.addInstructionsBC(self.txt_pos6,

1477 ("Visina: "+'%.2f' %(self.y)+" m\nBrzina: " +

1478 '%.2f' %(self.v)+" m/s\nAkceleracija: " +

1479 '%.2f' %(self.a)+" m/(s*s)\nSila na padobranca: " +

1480 '%.2f' %(self.m * self.a)+" N\nVrijeme: " +

1481 '%.2f' %(self.t) +

1482 " s\nVjetar ne djeluje na crvenog padobranca"))

1483 else:

1484 self.data_l = self.addInstructionsTL(self.txt_pos1,

1485 ("Visina: "+'%.2f' %(self.y)+" m\nBrzina: " +

1486 '%.2f' %(self.v)+" m/s\nAkceleracija: " +

1487 '%.2f' %(self.a)+" m/(s*s)\nSila na padobranca: " +

1488 '%.2f' %(self.m * self.a)+" N\nVrijeme: " +

1489 '%.2f' %(self.t)+" s"))

1490

1491 def updateReadoutWind(self):

1492 try:

1493 self.wind_txt

1494 except:

1495 pass

1496 else:

1497 self.wind_txt.destroy()

1498 if (self.TwoSkydivers == True):

1499 if (self.flag == 5 or self.ws == 0):

1500 self.wind_txt = self.addInstructionsBL(self.txt_pos5,

1501 "Brzina vjetra: 0 m/s")

1502 else:

1503 self.wind_txt = self.addInstructionsBL(self.txt_pos5,

1504 "Brzina vjetra: "+'%.2f' %(self.ws) +

1505 " m/s u smjeru " + self.dir)

1506 else:

1507 if (self.flag == 5 or self.ws == 0):

1508 self.wind_txt = self.addInstructionsTL(self.txt_pos2,

58

1509 "Brzina vjetra: 0 m/s")

1510 else:

1511 self.wind_txt = self.addInstructionsTL(self.txt_pos2,

1512 "Brzina vjetra: "+'%.2f' %(self.ws) +

1513 " m/s u smjeru "+self.dir)

1514 if(self.jumpOver == True):

1515 self.catchWind()

1516

1517 def clearReadout(self):

1518 self.catchVariables()

1519 taskMgr.doMethodLater(0.25, self.wipeWindText, "wipe wind")

1520 self.data_l.destroy()

1521 self.debrief = self.addInstructionsTL(self.txt_pos1,

1522 ("Skok je uspjesno izvrsen!\n\nPritisnite space za" +

1523 " graficku analizu skoka\nili esc za povratak u " +

1524 "glavni izbornik"))

1525 if (self.TwoSkydivers == True):

1526 self.data_r.destroy()

1527 self.accept("space", self.analyseJump)

1528

1529 def clearReadout2(self):

1530 self.catchVariables()

1531 taskMgr.doMethodLater(0.25, self.wipeWindText, "wipe wind")

1532 self.data_l.destroy()

1533 self.debrief = self.addInstructionsTL(self.txt_pos1,

1534 ("Skok je neuspjesno izvrsen - posljedice su fatalne!" +

1535 "\n\nPritisnite space za graficku analizu skoka\nili" +

1536 " esc za povratak u glavni izbornik"))

1537 if (self.TwoSkydivers == True):

1538 self.data_r.destroy()

1539 self.accept("space", self.analyseJump)

1540

1541 def clearEntireReadout(self):

1542 self.data_l.destroy()

1543 self.catchVariables()

1544 self.help_txt.destroy()

1545 if (self.TwoSkydivers == True):

1546 self.data_r.destroy()

1547

1548 # Following methods are used for setting simulation display and cameras.

1549 # Naredne metode se koriste za postavljanje kamera i prikaza simulacije.

1550

1551 def makeRegion(self, cam, dimensions, color):

1552 self.region = cam.node().getDisplayRegion(0)

1553 self.region.setDimensions(dimensions.getX(), dimensions.getY(),

1554 dimensions.getZ(), dimensions.getW())

1555 self.region.setClearColor(color)

1556 self.region.setClearColorActive(True)

1557 aspect = float(self.region.getPixelWidth()) / float(

59

1558 self.region.getPixelHeight())

1559 cam.node().getLens().setAspectRatio(aspect)

1560

1561 def makeRegion2(self, cam, dimensions, color):

1562 self.region2 = cam.node().getDisplayRegion(0)

1563 self.region2.setDimensions(dimensions.getX(), dimensions.getY(),

1564 dimensions.getZ(), dimensions.getW())

1565 self.region2.setClearColor(color)

1566 self.region2.setClearColorActive(True)

1567 aspect = float(self.region2.getPixelWidth()) / float(

1568 self.region2.getPixelHeight())

1569 cam.node().getLens().setAspectRatio(aspect)

1570

1571 def cameraReset(self):

1572 if (self.TwoSkydivers == True):

1573 self.cameras[self.active_cam].setPos(0, -40, 0.5)

1574 self.cameras[self.active_cam].setHpr(0, 0, 0)

1575 self.angleDegrees2c[self.active_cam] = 0

1576 self.angleDegrees2c2[self.active_cam] = 0

1577 self.cam_dists[self.active_cam] = 40

1578 self.cam_heights[self.active_cam] = 0.5

1579 else:

1580 self.camera.setPos(0, -40, 0.5)

1581 self.camera.setHpr(0, 0, 0)

1582 self.angleDegrees = 0

1583 self.angleDegrees2 = 0

1584 self.cam_dist = 40

1585 self.cam_height = 0.5

1586

1587 def cameraZoom(self, dir, dt):

1588 if(dir == "in"):

1589 self.camera.setY(self.camera, 75 * dt)

1590 else:

1591 self.camera.setY(self.camera, -75 * dt)

1592 self.cam_height = self.camera.getZ()

1593 self.cam_dist = math.sqrt(math.pow(self.camera.getDistance(

1594 self.skydiver), 2) - math.pow(self.cam_height, 2))

1595

1596 def cameraRot(self, dir):

1597 if(dir == "right"):

1598 self.angleDegrees += 1

1599 else:

1600 self.angleDegrees -= 1

1601 self.angleRadians = self.angleDegrees * (math.pi / 180.0)

1602 self.camera.setPos(self.cam_dist * math.sin(self.angleRadians),

1603 -self.cam_dist * math.cos(self.angleRadians),

1604 self.cam_height)

1605 self.camera.setH(self.angleDegrees)

1606

60

1607 def cameraRot2(self, dir):

1608 if(dir == "up"):

1609 self.angleDegrees2 += 1

1610 else:

1611 self.angleDegrees2 -= 1

1612 self.camera.setP(self.angleDegrees2)

1613

1614 def cameraZooms(self, dir, dt):

1615 if(dir == "in"):

1616 self.cameras[self.active_cam].setY(self.cameras[self.active_cam],

1617 75 * dt)

1618 else:

1619 self.cameras[self.active_cam].setY(self.cameras[self.active_cam],

1620 -75 * dt)

1621 self.cam_heights[1] = self.cameras[1].getZ()

1622 self.cam_dists[1] = math.sqrt(math.pow(self.cameras[1].getDistance(

1623 self.skydiver2), 2)

1624 - math.pow(self.cam_heights[1], 2))

1625 self.cam_heights[0] = self.cameras[0].getZ()

1626 self.cam_dists[0] = math.sqrt(math.pow(self.cameras[0].getDistance(

1627 self.skydiver3), 2)

1628 - math.pow(self.cam_heights[0], 2))

1629

1630 def cameraRots(self, dir):

1631 if(dir == "right"):

1632 self.angleDegrees2c[self.active_cam] += 1

1633 else:

1634 self.angleDegrees2c[self.active_cam] -= 1

1635 self.angleRadians = self.angleDegrees2c[self.active_cam] * (math.pi

1636 / 180.0)

1637 self.cameras[self.active_cam].setPos((self.cam_dists[self.active_cam]

1638 * math.sin(self.angleRadians)),

1639 (-self.cam_dists[self.active_cam]

1640 * math.cos(self.angleRadians)),

1641 self.cam_heights[self.active_cam])

1642 self.cameras[self.active_cam].setH(self.angleDegrees2c[self.active_cam])

1643

1644 def cameraRots2(self, dir):

1645 if(dir == "up"):

1646 self.angleDegrees2c2[self.active_cam] += 1

1647 else:

1648 self.angleDegrees2c2[self.active_cam] -= 1

1649 self.cameras[self.active_cam].setP(self.angleDegrees2c2[

1650 self.active_cam])

1651

1652 def isee(self,task):

1653 dt = globalClock.getDt()

1654 if(self.keyMap["mouse1"] == True):

1655 self.cameraZoom("in", dt)

61

1656 elif(self.keyMap["mouse3"] == True):

1657 self.cameraZoom("out", dt)

1658 elif(self.keyMap["arrow_up"] == True):

1659 self.cameraRot2("up")

1660 elif(self.keyMap["arrow_down"] == True):

1661 self.cameraRot2("down")

1662 elif(self.keyMap["arrow_left"] == True):

1663 self.cameraRot("left")

1664 elif(self.keyMap["arrow_right"] == True):

1665 self.cameraRot("right")

1666 elif(self.keyMap["page_up"] == True):

1667 self.cameraZoom("in", dt)

1668 elif(self.keyMap["page_down"] == True):

1669 self.cameraZoom("out", dt)

1670 return task.cont

1671

1672 def isee2(self,task):

1673 dt = globalClock.getDt()

1674 if(self.keyMap["mouse1"] == True):

1675 self.cameraZooms("in", dt)

1676 elif(self.keyMap["mouse3"] == True):

1677 self.cameraZooms("out", dt)

1678 elif(self.keyMap["arrow_up"] == True):

1679 self.cameraRots2("up")

1680 elif(self.keyMap["arrow_down"] == True):

1681 self.cameraRots2("down")

1682 elif(self.keyMap["arrow_left"] == True):

1683 self.cameraRots("left")

1684 elif(self.keyMap["arrow_right"] == True):

1685 self.cameraRots("right")

1686 elif(self.keyMap["page_up"] == True):

1687 self.cameraZooms("in", dt)

1688 elif(self.keyMap["page_down"] == True):

1689 self.cameraZooms("out", dt)

1690 return task.cont

1691

1692 def setKey(self, key, value):

1693 self.keyMap[key] = value

1694

1695 def activeL(self):

1696 self.active_cam = 0

1697

1698 def activeR(self):

1699 self.active_cam = 1

1700

1701 # Following methods are used for setting wind in simulation.

1702 # Naredne metode se koriste za postavljanje vjetra u simulaciji.

1703

1704 def windSW(self):

62

1705 self.wsx = -self.ws / math.sqrt(2)

1706 self.wsy = -self.ws / math.sqrt(2)

1707 self.flag = 1

1708 self.dir ="jugozapada"

1709

1710 def windS(self):

1711 self.wsx = 0

1712 self.wsy = -self.ws

1713 self.flag = 2

1714 self.dir ="juga"

1715

1716 def windSE(self):

1717 self.wsx = self.ws / math.sqrt(2)

1718 self.wsy = -self.ws / math.sqrt(2)

1719 self.flag = 3

1720 self.dir ="jugoistoka"

1721

1722 def windW(self):

1723 self.wsx = -self.ws

1724 self.wsy = 0

1725 self.flag = 4

1726 self.dir ="zapada"

1727

1728 def windNone(self):

1729 self.wsx = 0

1730 self.wsy = 0

1731 self.flag = 5

1732

1733 def windE(self):

1734 self.wsx = self.ws

1735 self.wsy = 0

1736 self.flag = 6

1737 self.dir ="istoka"

1738

1739 def windNW(self):

1740 self.wsx = -self.ws / math.sqrt(2)

1741 self.wsy = self.ws / math.sqrt(2)

1742 self.flag = 7

1743 self.dir ="sjeverozapada"

1744

1745 def windN(self):

1746 self.wsx = 0

1747 self.wsy = self.ws

1748 self.flag = 8

1749 self.dir ="sjevera"

1750

1751 def windNE(self):

1752 self.wsx = self.ws / math.sqrt(2)

1753 self.wsy = self.ws / math.sqrt(2)

63

1754 self.flag = 9

1755 self.dir ="sjeveroistoka"

1756

1757 def windSpeedIncr(self):

1758 if (self.ws < 2.5):

1759 self.ws+=0.5

1760 if (self.flag == 1):

1761 self.windSW()

1762 if (self.flag == 2):

1763 self.windS()

1764 if (self.flag == 3):

1765 self.windSE()

1766 if (self.flag == 4):

1767 self.windW()

1768 if (self.flag == 5):

1769 self.windNone()

1770 if (self.flag == 6):

1771 self.windE()

1772 if (self.flag == 7):

1773 self.windNW()

1774 if (self.flag == 8):

1775 self.windN()

1776 if (self.flag == 9):

1777 self.windNE()

1778

1779 def windSpeedDecr(self):

1780 if (self.ws > 0):

1781 self.ws-=0.5

1782 if (self.flag == 1):

1783 self.windSW()

1784 if (self.flag == 2):

1785 self.windS()

1786 if (self.flag == 3):

1787 self.windSE()

1788 if (self.flag == 4):

1789 self.windW()

1790 if (self.flag == 5):

1791 self.windNone()

1792 if (self.flag == 6):

1793 self.windE()

1794 if (self.flag == 7):

1795 self.windNW()

1796 if (self.flag == 8):

1797 self.windN()

1798 if (self.flag == 9):

1799 self.windNE()

1800

1801 def wind(self, task):

1802 dt = globalClock.getDt()

64

1803 if (dt > .50):

1804 return task.cont

1805 self.skydiver.setX(self.skydiver,self.wsx * dt)

1806 self.skydiver.setY(self.skydiver,self.wsy * dt)

1807 self.baloon.setX(self.baloon,-self.wsx * dt * 5)

1808 self.baloon.setY(self.baloon,-self.wsy * dt * 5)

1809 self.updateReadoutWind()

1810 return task.cont

1811

1812 def wind2(self, task):

1813 dt = globalClock.getDt()

1814 if (dt > .50):

1815 return task.cont

1816 self.skydiver3.setX(self.skydiver3,self.wsx * dt)

1817 self.skydiver3.setY(self.skydiver3,self.wsy * dt)

1818 self.updateReadoutWind()

1819 return task.cont

1820

1821 # Following methods are used for setting collision and its aftermath.

1822 # Naredne metode se koriste za postavljanje kolizije i svega sto iza

1823 # nje slijedi.

1824

1825 def setupCD(self):

1826 base.cTrav = CollisionTraverser()

1827 self.notifier = CollisionHandlerEvent()

1828 self.notifier.addInPattern("%fn-in-%in")

1829 self.accept("skydiver-in-floor", self.onCollision)

1830 self.accept("skydiver2-in-floor", self.onCollision)

1831

1832 def onCollision(self, entry):

1833 self.killSwitch()

1834 if (self.opened_chute == True):

1835 self.adjust()

1836

1837 def killSwitch(self):

1838 self.jumpOver = True

1839 taskMgr.removeTasksMatching("wind1")

1840 taskMgr.removeTasksMatching("wind2")

1841 taskMgr.doMethodLater(0.25, self.wipeWindText, "wipe wind")

1842 self.accept("space", self.analyseJump)

1843

1844 def analyseJump(self):

1845 self.useSimulationData = True

1846 self.endSimulation()

1847

1848 def wipeWindText(self, task):

1849 self.catchWind()

1850 return task.done

1851

65

1852 # Following methods are used for triggering various stages of jump and

1853 # appropriate behaviours and calculations.

1854 # Naredne metode se koriste za aktiviranje specificnih faza u skoku te

1855 # odgovorajucih proracuna i radnji.

1856

1857 def setSpaceKey(self):

1858 self.skydiver.play("open_chute", fromFrame=1, toFrame=260)

1859 if (self.air_drag == False):

1860 taskMgr.doMethodLater(1, self.jumpBO, "jumpBO")

1861 else:

1862 taskMgr.doMethodLater(1, self.noDrag, "noDrag")

1863 self.accept("space", self.setSpaceKeyAgain)

1864

1865 def setSpaceKey2(self):

1866 self.skydiver2.play("open_chute", fromFrame=1, toFrame=260)

1867 self.skydiver3.play("open_chute", fromFrame=1, toFrame=260)

1868 taskMgr.doMethodLater(1, self.jumpBO2, "jumpBO2")

1869 taskMgr.doMethodLater(1, self.wind2,"wind2")

1870 self.accept("space", self.setSpaceKeyAgain2)

1871

1872 def setSpaceKeyAgain(self):

1873 taskMgr.remove("jumpBO")

1874 self.t0 = self.t

1875 self.t1 = self.t + 0.5

1876 self.t2 = self.t + 1.5

1877 self.t3 = self.t + 3.2

1878 self.skydiver.play("open_chute", fromFrame=260, toFrame = 500)

1879 if (self.air_drag == False):

1880 taskMgr.add(self.jumpAO, "jumpAO")

1881 self.accept("space", self.setSpaceKeyYetAgain)

1882 self.opened_chute = True

1883

1884 def setSpaceKeyAgain2(self):

1885 taskMgr.remove("jumpBO2")

1886 self.t0 = self.t

1887 self.t1 = self.t + 0.5

1888 self.t2 = self.t + 1.5

1889 self.t3 = self.t + 3.2

1890 self.skydiver2.play("open_chute", fromFrame=260, toFrame = 500)

1891 self.skydiver3.play("open_chute", fromFrame=260, toFrame = 500)

1892 taskMgr.add(self.jumpAO2, "jumpAO2")

1893 self.accept("space", self.setSpaceKeyYetAgain)

1894 self.opened_chute = True

1895

1896 def setSpaceKeyYetAgain(self):

1897 pass

1898

1899 # Following methods are used for data manipulation and analysis.

1900 # Naredne metode se koriste za analizu i manipuliranje podacima.

66

1901

1902 def dump(self):

1903 self.updateReadout()

1904 self.lst_time.append(self.t)

1905 self.lst_acceleration.append(self.a)

1906 self.lst_velocity.append(self.v)

1907 self.lst_position.append(self.y)

1908

1909 def dump2(self):

1910 self.lst_time.append(self.t)

1911 self.lst_acceleration.append(self.a)

1912 self.lst_velocity.append(self.v)

1913 self.lst_position.append(self.y)

1914

1915 def graphs(self):

1916 self.g_a = plt.figure()

1917 self.g_a.canvas.set_window_title("Graf: akceleracija u vremenu")

1918 self.g_v = plt.figure()

1919 self.g_v.canvas.set_window_title("Graf: brzina u vremenu")

1920 self.g_p = plt.figure()

1921 self.g_p.canvas.set_window_title("Graf: polozaj u vremenu")

1922 self.graph_acc = self.g_a.add_subplot(111)

1923 self.graph_acc.plot(self.lst_time,self.lst_acceleration)

1924 self.graph_acc.set_xlabel('Vrijeme [s]')

1925 self.graph_acc.set_ylabel('Akceleracija [m/(s*s)]')

1926 self.graph_vel = self.g_v.add_subplot(111)

1927 self.graph_vel.plot(self.lst_time,self.lst_velocity)

1928 self.graph_vel.set_xlabel('Vrijeme [s]')

1929 self.graph_vel.set_ylabel('Brzina [m/s]')

1930 self.graph_pos = self.g_p.add_subplot(111)

1931 self.graph_pos.plot(self.lst_time,self.lst_position)

1932 self.graph_pos.set_xlabel('Vrijeme [s]')

1933 self.graph_pos.set_ylabel('Polozaj [m]')

1934 plt.show()

1935

1936 # Following methods are used for creating and positioning environment and

1937 # models.

1938 # Naredne metode se koriste za kreiranje i pozicioniranje okolisa i modela.

1939

1940 def adjust(self):

1941 if (self.TwoSkydivers == True):

1942 self.skydiver2.play("open_chute", fromFrame=540, toFrame=560)

1943 self.skydiver3.play("open_chute", fromFrame=540, toFrame=560)

1944 else:

1945 self.skydiver.play("open_chute", fromFrame=540, toFrame=560)

1946

1947 def createEnvironmentCommon(self):

1948 self.floor = render.attachNewNode(CollisionNode("floor"))

1949 self.floor.node().addSolid(CollisionPlane(Plane(Vec3(0, 0, 1),

67

1950 Point3(0, 0, 0))))

1951 self.skysphere = loader.loadModel('models/sunny.egg')

1952 self.skysphere.setPos(self.worldsize / 2, self.worldsize / 2, 1250)

1953 self.skysphere.setScale(12)

1954 self.skysphere.reparentTo(self.scene3)

1955 self.baloon = loader.loadModel('models/balon.egg')

1956 self.baloon.setH(180)

1957 self.baloon.setSx(0.4)

1958 self.baloon.setSy(0.4)

1959 self.baloon.setSz(0.4)

1960 self.baloon.reparentTo(self.scene3)

1961

1962 def resetWorldObjects(self):

1963 if (self.TwoSkydivers == True):

1964 self.baloon.setPos((self.worldsize / 2) + 26.4,

1965 (self.worldsize / 2) + 3.5,

1966 self.height + 5.1 + self.correction)

1967 self.baloon2.setPos((self.worldsize / 2) - 26.4,

1968 (self.worldsize / 2) + 3.5,

1969 self.height + 5.1 + self.correction)

1970 else:

1971 self.baloon.setPos((self.worldsize / 2) + 1.4, (self.worldsize / 2)

1972 + 3.5, self.height + 5.1 + self.correction)

1973 self.skydiver.pose('open_chute', 1)

1974 self.skydiver.setPos(self.worldsize / 2, self.worldsize / 2,

1975 self.height + self.correction)

1976 self.skydiver2.pose('open_chute', 1)

1977 self.skydiver2.setPos(self.worldsize / 2 + 25, self.worldsize / 2,

1978 self.height + self.correction)

1979 self.skydiver3.pose('open_chute', 1)

1980 self.skydiver3.setPos(self.worldsize / 2 - 27.75, self.worldsize / 2,

1981 self.height + self.correction)

1982

1983 def createEnvironment(self):

1984 self.sand = loader.loadModel('models/square.egg')

1985 self.sand.setSx(self.worldsize * 5)

1986 self.sand.setSy(self.worldsize * 5)

1987 self.sand.setPos(self.worldsize / 2, self.worldsize / 2, 0)

1988 self.sTS = TextureStage('1')

1989 self.sand.setTexture(self.sTS, loader.loadTexture('models/sand.jpg'))

1990 self.sand.setTexScale(self.sTS, 16)

1991 self.sand.reparentTo(self.scene1)

1992 self.skydiver = Actor("models/padobranac.egg",

1993 {"open_chute": "models/padobranac-anim.egg"})

1994 self.skydiver.setTwoSided(1)

1995 self.skydiver.pose('open_chute', 1)

1996 self.skydiver.setPos(self.worldsize / 2, self.worldsize / 2,

1997 self.height + self.correction)

1998 self.skydiver.setSx(2)

68

1999 self.skydiver.setSy(2)

2000 self.skydiver.setSz(2)

2001 self.skydiver.reparentTo(self.scene1)

2002 self.col = self.skydiver.attachNewNode(CollisionNode("skydiver"))

2003 self.col.node().addSolid(CollisionSphere(0, -3.4, -4.35, 1.1))

2004 base.cTrav.addCollider(self.col, self.notifier)

2005 self.palm = loader.loadModel('models/Palm_Tree.egg')

2006 random.seed()

2007 for i in range(350):

2008 self.placeholder = self.scene1.attachNewNode(PandaNode(

2009 "palm-Placeholder"))

2010 place1 = random.randint(-1600, 2600)

2011 place2 = random.randint(-1600, 2600)

2012 size = random.randint(2,5)

2013 self.placeholder.setScale(size, size, size)

2014 self.placeholder.setPos(place1, place2, 8 + (3 * size))

2015 self.palm.instanceTo(self.placeholder)

2016

2017 def createEnvironment2(self):

2018 self.target = loader.loadModel('models/square.egg')

2019 self.target.setSx(self.worldsize * 5)

2020 self.target.setSy(self.worldsize * 5)

2021 self.target.setPos(self.worldsize / 2, self.worldsize / 2, 0)

2022 self.tTS = TextureStage('1')

2023 self.target.setTexture(self.tTS, loader.loadTexture('models/krug.png'))

2024 self.target.setTexScale(self.tTS, 1)

2025 self.target.reparentTo(self.scene2)

2026 self.baloon2 = loader.loadModel('models/balon2.egg')

2027 self.baloon2.setH(180)

2028 self.baloon2.setSx(0.4)

2029 self.baloon2.setSy(0.4)

2030 self.baloon2.setSz(0.4)

2031 self.baloon2.reparentTo(self.scene2)

2032 self.skydiver2 = Actor("models/padobranac_c.egg",

2033 {"open_chute": "models/padobranac_c-anim.egg"})

2034 self.skydiver2.setTwoSided(1)

2035 self.skydiver2.pose('open_chute', 1)

2036 self.skydiver2.setPos(self.worldsize / 2 + 25, self.worldsize / 2,

2037 self.height + self.correction)

2038 self.skydiver2.setSx(2)

2039 self.skydiver2.setSy(2)

2040 self.skydiver2.setSz(2)

2041 self.skydiver2.reparentTo(self.scene2)

2042 self.col2 = self.skydiver2.attachNewNode(CollisionNode("skydiver2"))

2043 self.col2.node().addSolid(CollisionSphere(0, -3.4, -4.35, 1.1))

2044 base.cTrav.addCollider(self.col2, self.notifier)

2045 self.skydiver3 = Actor("models/padobranac_p.egg",

2046 {"open_chute": "models/padobranac_p-anim.egg"})

2047 self.skydiver3.setTwoSided(1)

69

2048 self.skydiver3.pose('open_chute', 1)

2049 self.skydiver3.setPos(self.worldsize / 2 - 27.75, self.worldsize / 2,

2050 self.height + self.correction)

2051 self.skydiver3.setSx(2)

2052 self.skydiver3.setSy(2)

2053 self.skydiver3.setSz(2)

2054 self.skydiver3.reparentTo(self.scene2)

2055

2056 # A method for setting global illumination.

2057 # Metoda za postavljanje globalne iluminacije.

2058

2059 def lightsON(self):

2060 ambientLight = AmbientLight("ambientLight")

2061 ambientLight.setColor(Vec4(1, 1, 1, 1))

2062 directionalLight = DirectionalLight("directionalLight")

2063 directionalLight.setDirection(Vec3(0,0,-1))

2064 directionalLight.setColor(Vec4(0.9, 0.9, 0.9, 1))

2065 directionalLight.setSpecularColor(Vec4(0.9, 0.9, 0.9, 1))

2066 render.setLight(render.attachNewNode(ambientLight))

2067 render.setLight(render.attachNewNode(directionalLight))

2068

2069 # Following methods are used for calculating physical aspects of simulation.

2070 # Naredne metode se koriste za fizikalne proracune u simulaciji.

2071

2072 def velocity(self):

2073 self.v = (self.v + (self.a * self.dt))

2074

2075 def acceleration(self):

2076 self.a = (-self.g + (self.fd / self.m * self.v * self.v))

2077

2078 def position(self):

2079 self.y = (self.y + (self.v * self.dt) +

2080 (0.5 * self.a * self.dt * self.dt))

2081

2082 def calculateVT(self):

2083 self.vt = math.sqrt(self.m * self.g / self.k1())

2084

2085 def alfa0(self):

2086 self.temp = 0

2087 self.temp = ((1.95 * self.b0) + (0.35 * self.b1 * (1-self.h))) / 1.33

2088 return self.temp

2089

2090 def beta0(self):

2091 self.temp = 0

2092 self.temp = math.log1p(self.a1 / self.alfa0())

2093 return self.temp

2094

2095 def k1(self):

2096 self.temp = 0

70

2097 self.temp = 0.5 * (1.95 * self.b0)

2098 return self.temp

2099

2100 def k2(self):

2101 self.temp = 0

2102 self.temp = 0.5 * ((1.95 * self.b0) + (0.35 * self.b1 * self.l *

2103 ((self.t-self.t0) / (self.t1-self.t0))))

2104 return self.temp

2105

2106 def k3(self):

2107 self.temp = 0

2108 self.temp = 0.5 * ((0.35 * self.b1 * self.h) + (1.33 * self.ae12()))

2109 return self.temp

2110

2111 def k4(self):

2112 self.temp = 0

2113 self.temp = 0.5 * ((0.35 * self.b1 * self.h) + (1.33 * self.ae23()))

2114 return self.temp

2115

2116 def k5(self):

2117 self.temp = 0

2118 self.temp = 0.5 * ((0.35 * self.b1 * self.h) + (1.33 * self.a1))

2119 return self.temp

2120

2121 def ae12(self):

2122 self.temp = 0

2123 self.temp = self.sa0 * math.exp(self.sb0 * ((self.t-self.t1) /

2124 (self.t2-self.t1)))

2125 return self.temp

2126

2127 def ae23(self):

2128 self.temp = 0

2129 self.temp = self.a1 * (1 + (self.beta1 * math.sin(3.14 *

2130 ((self.t-self.t2) / (self.t3-self.t2)))))

2131 return self.temp

2132

2133 def jumpBO(self, task):

2134 dt = globalClock.getDt()

2135 poc = 0

2136 if (dt > .50):

2137 return task.cont

2138 while(poc <= dt):

2139 self.t += self.dt

2140 poc += self.dt

2141 self.fd = self.k1()

2142 self.acceleration()

2143 self.velocity()

2144 self.position()

2145 if (self.y > 0):

71

2146 self.skydiver.setZ(self.y + self.correction)

2147 self.dump()

2148 if (self.y <= 0):

2149 self.clearReadout2()

2150 return task.done

2151 return task.cont

2152

2153 def jumpAO(self, task):

2154 dt = globalClock.getDt()

2155 poc = 0

2156 if (dt > .50):

2157 return task.cont

2158 if ((self.t <= self.t1) and (self.t >= self.t0)):

2159 while(poc <= dt):

2160 self.t += self.dt

2161 poc += self.dt

2162 self.fd = self.k2()

2163 self.acceleration()

2164 self.velocity()

2165 self.position()

2166 self.aend = self.a

2167 if (self.y > 0):

2168 self.skydiver.setZ(self.y + self.correction)

2169 self.dump()

2170 if (self.y <= 0):

2171 self.clearReadout2()

2172 return task.done

2173 return task.cont

2174 elif ((self.t <= self.t2) and (self.t >= self.t1)):

2175 while(poc <= dt):

2176 self.t += self.dt

2177 poc += self.dt

2178 self.fd = self.k3()

2179 self.acceleration()

2180 if (self.a < self.aend):

2181 self.a = self.aend

2182 self.velocity()

2183 self.position()

2184 if (self.y > 0):

2185 self.skydiver.setZ(self.y + self.correction)

2186 self.dump()

2187 if (self.y <= 0):

2188 self.clearReadout2()

2189 return task.done

2190 return task.cont

2191 elif ((self.t <= self.t3) and (self.t >= self.t2)):

2192 while(poc <= dt):

2193 self.t += self.dt

2194 poc += self.dt

72

2195 self.fd = self.k4()

2196 self.acceleration()

2197 self.velocity()

2198 self.position()

2199 if (self.y > 0):

2200 self.skydiver.setZ(self.y + self.correction)

2201 self.dump()

2202 if (self.y <= 0):

2203 self.clearReadout2()

2204 return task.done

2205 return task.cont

2206 elif (self.t >= self.t3):

2207 while(poc <= dt):

2208 self.t += self.dt

2209 poc += self.dt

2210 self.fd = self.k5()

2211 self.acceleration()

2212 self.velocity()

2213 self.position()

2214 if (self.y > 0):

2215 self.skydiver.setZ(self.y + self.correction)

2216 self.dump()

2217 if (self.y <= 0):

2218 self.clearReadout()

2219 return task.done

2220 return task.cont

2221 else:

2222 return task.cont

2223

2224 def jumpBO2(self, task):

2225 dt = globalClock.getDt()

2226 poc = 0

2227 if (dt > .50):

2228 return task.cont

2229 while(poc <= dt):

2230 self.t += self.dt

2231 poc += self.dt

2232 self.fd = self.k1()

2233 self.acceleration()

2234 self.velocity()

2235 self.position()

2236 if (self.y > 0):

2237 self.skydiver2.setZ(self.y + self.correction)

2238 self.skydiver3.setZ(self.y + self.correction)

2239 self.dump()

2240 if (self.y <= 0):

2241 self.clearReadout2()

2242 return task.done

2243 return task.cont

73

2244

2245 def jumpAO2(self, task):

2246 dt = globalClock.getDt()

2247 poc = 0

2248 if (dt > .50):

2249 return task.cont

2250 if ((self.t <= self.t1) and (self.t >= self.t0)):

2251 while(poc <= dt):

2252 self.t += self.dt

2253 poc += self.dt

2254 self.fd = self.k2()

2255 self.acceleration()

2256 self.velocity()

2257 self.position()

2258 self.aend = self.a

2259 if (self.y > 0):

2260 self.skydiver2.setZ(self.y + self.correction)

2261 self.skydiver3.setZ(self.y + self.correction)

2262 self.dump()

2263 if (self.y <= 0):

2264 self.clearReadout2()

2265 return task.done

2266 return task.cont

2267 elif ((self.t <= self.t2) and (self.t >= self.t1)):

2268 while(poc <= dt):

2269 self.t += self.dt

2270 poc += self.dt

2271 self.fd = self.k3()

2272 self.acceleration()

2273 if (self.a < self.aend):

2274 self.a = self.aend

2275 self.velocity()

2276 self.position()

2277 if (self.y > 0):

2278 self.skydiver2.setZ(self.y + self.correction)

2279 self.skydiver3.setZ(self.y + self.correction)

2280 self.dump()

2281 if (self.y <= 0):

2282 self.clearReadout2()

2283 return task.done

2284 return task.cont

2285 elif ((self.t <= self.t3) and (self.t >= self.t2)):

2286 while(poc <= dt):

2287 self.t += self.dt

2288 poc += self.dt

2289 self.fd = self.k4()

2290 self.acceleration()

2291 self.velocity()

2292 self.position()

74

2293 if (self.y > 0):

2294 self.skydiver2.setZ(self.y + self.correction)

2295 self.skydiver3.setZ(self.y + self.correction)

2296 self.dump()

2297 if (self.y <= 0):

2298 self.clearReadout2()

2299 return task.done

2300 return task.cont

2301 elif (self.t >= self.t3):

2302 while(poc <= dt):

2303 self.t += self.dt

2304 poc += self.dt

2305 self.fd = self.k5()

2306 self.acceleration()

2307 self.velocity()

2308 self.position()

2309 if (self.y > 0):

2310 self.skydiver2.setZ(self.y + self.correction)

2311 self.skydiver3.setZ(self.y + self.correction)

2312 self.dump()

2313 if (self.y <= 0):

2314 self.clearReadout()

2315 return task.done

2316 return task.cont

2317 else:

2318 return task.cont

2319

2320 def noDrag(self, task):

2321 dt = globalClock.getDt()

2322 poc = 0

2323 if (dt > .50):

2324 return task.cont

2325

2326 while(poc <= dt):

2327 self.t += self.dt

2328 poc += self.dt

2329 self.velocity()

2330 self.position()

2331 if (self.y > 0):

2332 self.skydiver.setZ(self.y + self.correction)

2333 self.dump()

2334 if (self.y <= 0):

2335 self.clearReadout2()

2336 return task.done

2337 return task.cont

2338

2339 def calculateNoDrag(self):

2340 while (self.t <= self.tk):

2341 if (self.y <=0):

75

2342 break

2343 self.t += self.dt

2344 self.velocity()

2345 self.position()

2346 self.dump2()

2347

2348 def calculateNoOpening(self):

2349 while (self.t <= self.tk):

2350 if (self.y <=0):

2351 break

2352 self.t += self.dt

2353 self.fd = self.k1()

2354 self.acceleration()

2355 self.velocity()

2356 self.position()

2357 self.dump2()

2358

2359 def calculate(self):

2360 while (self.t <= self.t0):

2361 if (self.y <=0):

2362 break

2363 self.t += self.dt

2364 self.fd = self.k1()

2365 self.acceleration()

2366 self.velocity()

2367 self.position()

2368 self.dump2()

2369 while (self.t <= self.t1):

2370 if (self.y <=0):

2371 break

2372 self.t += self.dt

2373 self.fd = self.k2()

2374 self.acceleration()

2375 self.velocity()

2376 self.position()

2377 self.dump2()

2378 self.aend = self.a

2379 while (self.t <= self.t2):

2380 if (self.y <=0):

2381 break

2382 self.t += self.dt

2383 self.fd = self.k3()

2384 self.acceleration()

2385 if (self.a < self.aend):

2386 self.a = self.aend

2387 self.velocity()

2388 self.position()

2389 self.dump2()

2390 while (self.t <= self.t3):

76

2391 if (self.y <=0):

2392 break

2393 self.t += self.dt

2394 self.fd = self.k4()

2395 self.acceleration()

2396 self.velocity()

2397 self.position()

2398 self.dump2()

2399 while (self.t <= self.tk):

2400 if (self.y <=0):

2401 break

2402 self.t += self.dt

2403 self.fd = self.k5()

2404 self.acceleration()

2405 self.velocity()

2406 self.position()

2407 self.dump2()

2408

2409

2410 # Creation of simulation instance and starting the program.

2411 # Kreiranje instance simulacije i pokretanje programa.

2412

2413 w = Simulation()

2414 run()

77

Literatura

[1] Hugh D. Young, Roger A. Freedman, A. Lewis Ford, Sears and Zemansky's UniversityPhysics with Modern Physics, 11th edition, Addison-Wesley, 2003

[2] Sears and Zemansky's University Physics with Modern Physicsonline topics, Topic 01: Projectile Motion with Air Resistancehttp://wps.aw.com/wps/media/objects/877/898586/topics/topic01.pdf

[3] Meade, D.B, Struthers, A.A, Di�erential equations in the new millenium: The parac-hute problem, International Journal for Engineering Education, 15(6), 417-424, 1999

[4] Panda3D online documentationhttp://www.panda3d.org/documentation.php

[5] Python v2.7.2 documentationhttp://docs.python.org/

78