Introduktion til Computer Grafik Projekt 1 Line Løfquist,Thor Prentow, Nikoline Vinkel Datalogisk Institut, Aarhus Universitet Åbogade 34, 8200 Aarhus N, Denmark 20071346, 20071952, 20071387 {u071346,prentow,u071387}@cs.au.dk 22. marts 2011
Indhold 1 Introduktion 1 2 Scenegraf 1 2.1 Kamera................................. 1 2.2 Lys.................................... 2 3 Klasser og Objekter 2 3.1 Main Filen................................ 2 3.2 Boid................................... 3 3.3 TextureHandler............................. 3 3.4 Fish og Shark.............................. 3 3.5 KeyHandler............................... 3 3.6 Aquarium................................ 3 4 Dynamisk Model 4 4.1 Fiskeflokken............................... 4 4.2 Finner og Haler............................. 5 4.3 Tang................................... 5 4.4 Haj.................................... 5 4.5 Synsfelt................................. 6 5 Geometrierne 6 5.1 Fisk................................... 6 5.2 Haj.................................... 7 5.3 Akvarium................................ 8 A Sådan køres projektet 8
1 Introduktion Denne rapport omhandler vores implementation af Boid modellen for en mængde elementer, samt implementation af interaktion med elementerne. Projektet består af et akvarium indeholdende forskellige elementer. Først og fremmest er der en flok fisk, som svømmer rundt. Vi har implementeret Boid modellen så fiskene opfører sig som en rigtig flok fisk. Udover fiskeflokken er der en haj, som kan styres ved hjælp af keyboardet. Når hajen kommer i nærheden af fiskeflokken flygter flokken væk fra hajen. I toppen af akvariet er der en dynamisk bølgende havoverflade, mens bunden er en statisk bølget overflade. På bunden er der små tangplanter som også er animerede så de bølger frem og tilbage. Udover at kunne styre hajen er det også muligt at ændre på kameraets position, således at man kan se verden fra hajens synspunkt eller fra en anden fisks synspunkt. Fælles for alle fiskene er at deres finner og haler er animerede, således at de ser ud til at svømme rigtigt. 2 Scenegraf I Figur 1 ses vores scenegraf. Den indeholder et akvarium med bund, sider, top og tang, der er en boid med fisk og desuden er der en haj. Nederst i grafen er fiskene der bliver tegnet af boiden, samt tangen der bliver tegnet af akvariet. Der er ikke en transformation mellem den enkelte fisk/tang og boiden/akvariet da fiskene og tangen er lavet så de vender rigtigt når de bliver tegnet, og derfor ikke skal vendes. Boiden/akvariet har et array med forudbestemte positioner for hver fisk/tang. Derfor bestemmer boiden/akvariet hvor fisk/tang bliver tegnet. Fra boid/akvarie til scenen sker der ingen transformationer. Boiden er ikke et objekt i sig selv, men en klasse der holder styr på andre objekter og har derfor ingen fysisk repræsentation der skal transformeres. Akvariet bliver lavet ud af firkanter, der er transformeret til de korrekte positioner, men selve akvariet bliver ikke transformeret. Fra shark til scenen er der en transformation - nogen gange. Hajen kan selv bevæge sig rundt i vandet via en animation (se afsnit 4.4), men når man har trykket c overtager controlleren og hajen kan styres fra tastaturet. Mellem scenen og worldsystem er der en grabber til at frem og tilbage langs hhv. x-,y- eller z-aksen, eller rotere om disse. Desuden er der også mulighed for at stoppe tiden, ved at trykke p, samt starte den igen ved at trykke q. Fra world system bevæger vi os gennem en transformation til cameraspot, og det er ikke længere applikationen der styrer transformationerne men opengl. 2.1 Kamera Kameraet initialiseres til at være i positionen (0, 0, 2) og kigger i retning mod (0, 0, 0). Dette sker i Camera klassen, og den er ikke ændret i forhold til den udleverede klasse. Man har muligheden for et fish-cam hvor kameraet flyttes til at sidde bag en fisk i flokken. Dette gøres ved at trykke f på tastaturet. Det er ikke kameraet der flyttes, men verden der flyttes så fisken er lige foran kameraet, dette gøres af grabber-klassen. 1
2.2 Lys Lyskilden er placeret i (1.0, 0.7, 0.4). Lyskilden har typen ambient og farven (0.4, 0.4, 0.4, 1.0), soft white. Figur 1: Dette er vores scenegraf. 3 Klasser og Objekter Vi har i projektet brugt mange af de klasser vi har fået i løbet af kurset. Dette afsnit beskriver kort de vigtigste af de klasser vi selv har tilføjet og redigeret. 3.1 Main Filen Main filen indeholder de sædvanlige metoder init(), setlight(), idle(), draw(), display(), keyboard() og main(). Boid objektet, grabberen og hajen initialiseres i init(). I metoden laves også lidt tåge, således at hjørnerne i akvariet virker en smule mørkere og derved giver fornemmelsen af at være under vand. 2
display() metoden kalder preframe() metoderne på de animerede objekter. Mere om dette i afsnit 4. Derefter sørger metoden for at tegne et billede til højre øje og et billede til venstre øje. Den kalder draw() metoden som kalder de nødvendige metoder for at få alle objekterne tegnet. 3.2 Boid I Boid klassen har vi implementeret den opførsel der skal til for at fiskene opfører sig som en flok. Boid klassen initialiserer et givet antal fisk og placerer dem i et gitter. Boid klassen indeholder metoder som beregner henholdsvis separation, cohesion og direction ifølge Boid modellen. Mere om dette i afsnit 4. 3.3 TextureHandler TextureHandler klassen er en mindre klasse, som sørger for at indlæse alle de texturer som vores program bruger. Klassen benytter et singleton pattern for at sikre at vi kun læser hver textur ind en enkelt gang, og efterfølgende bruger dem ved at binde dem. Når vi skal bruge textures kaldes instance() på TextureHandler klassen for at få fat i singleton objektet, og vi har en lokal reference til alle texturene som vi kan bruge som vi vil. 3.4 Fish og Shark De to klasser minder meget om hinanden. Fish klassen indeholder metoder til at få tegnet en fisk. Metoderne benytter alle TextureHandler objektet således at fiskene ligner klovnefisk. I Shark klassen bruges nogle ekstra transformationer til at gøre den større end klovnefiskene. Desuden tegnes også en rygfinne. Klasserne har et Tumbler objekt, som bruges til at få fiskenes og hajens finner og hale til at bevæge sig. Begge klasser indeholder også en preframe() metode som sørger for at fiskene svømmer rundt. Mere om dette i afsnit 4. Da hajen kan styres med tastaturet, har Shark klassen en variabel, self_control, som enten er true eller false, alt efter om hajen svømmer selv eller styres med tastaturet. 3.5 KeyHandler Denne klasse har metoder der bliver kaldt i forbindelse med de registrerede metoder til gluts keyboard events. Den indeholder to mængder, en der holder styr på normale taster, og en til specialtaster såsom pil op. Når en tast bliver trykket ned bliver den tilføjet mængden, og når den bliver sluppet bliver den fjernet igen. Således kan vi altid spørge klassen om en bestemt tast er trykket ned. 3.6 Aquarium Den sidste klasse vi har tilføjet er Aquarium klassen. Denne klasse repræsenterer et akvarium. Akvariet består af fire vægge med samme texture på. Bunden og toppen i akvariet er begge instanser af klassen texturedoscsurface som vi har fået udleveret i løbet af kurset. Forskellen på dem er at bunden er frosset i tiden mens toppen bølger og derfor ligner en vandoverflade. På bunden af akvariet har vi placeret en masse små tangplanter, som også er animeret til at bølge frem og tilbage. 3
4 Dynamisk Model Som tidligere nævnt svømmer vores fisk rundt af sig selv og vi har tilføjet animationer for at få deres finner og haler til at bevæge sig. Dette afsnit beskriver detaljerne af disse animationer samt vores implementering af Boid modellen. 4.1 Fiskeflokken Vores fiskeflok er primært styret af tre kræfter som i Boid modellen. Alle fisk bliver styret på samme måde, hver fisk har dog et begrænset synsfelt og kan således kun se fisk inden for en bestemt radius rundt om sig selv. Hver fisk har tre tilgængelige vektorer; position, hastighed samt acceleration, der kan tilgåes af boid klassen. Vi udregner kræfterne der påvirker hver enkelt fisk individuelt, hvilket som resultat giver en accelerationsvektor til hver fisk. Vektoren er summen af følgende kræfter: Cohesion: For at holde flokken sammen er der en sammentrækkende kraft. For hver fisk udregner vi gennemsnitsppositionen for den del af flokken der er inden for fiskens synsradius. Vi lader fisken accelerere mod dette punkt ved at lade den accelerere i retningen givet ved vektoren der er fiskens egen position fratrukket gennemsnitspositionen. Seperation: For at undgå at flokken samler sig i et enkelt punkt er der en frastødende kraft blandt fiskene. For hver fisk finder vi den anden fisk der er tættest på. Er denne fisk inden for en givet afstand lader vi den accelerere i modsat retning, som er givet ved at substrahere positionerne af de to fisk. Vi normaliserer denne vektor og ganger den med en værdi i forhold til hvor tæt fiskene er, således at accelerationen væk fra den anden fisk bliver større jo tættere fiskene er. Direction: For at fiskene ikke blot svømmer rundt om hinanden i en klump har vi en retningsbestemmende kraft. Vi udregner gennemsnitsretning for alle fiskene. Fiskene accelererer så mod den samme retning, i form af en acceleration der er forskellen mellem fiskens retning og gennemsnitsretningen blandt fiskene i dens synsradius. For at få fiskeflokken til at se mere naturlig ud har vi visse andre kræfter der påvirker dem, som falder uden for boid modellen. For at fiskene ikke blot skal stå stille, og for at de skal bremse ned igen efter de er blevet skræmt af hajen sørger vi for at de altid konvergerer mod en default hastighed. Er deres hastighed, givet som længden af hastighedsvektoren, over eller under denne defaulthastighed vil de accelerere i modsat retning af deres hastighed, ganget med en konstant givet ved forskellen mellem nuværende og default hastighed. For at undgå at blive spist af hajen flygter fiskene fra hajen når den kommer inden for en hvis afstand. Teknikken i dette er den samme som fiskene holder sig fra hinanden med. Fiskene undgår sider og top/bund af akvariet på en tilsvarende måde. Når summen af alle disse kræfter er udregnet har vi en accelerationsvektor. Ud fra formlen v t = v 0 + a t udregner vi den nye hastighedsvektor af fisken ud fra accelerationen. Derefter udregner vi positionen som: p t = p o +v t t+ 1 2 a t2. For at fiskene ikke skal opnå en unaturlig høj hastighed tjekker vi derefter om længden af hastighedsvektoren er over en max-værdi, og er den det sætter vi den til maxværdien. 4
Skulle fiskene blive jaget ud til siderne af akvariet kan vi risikere at de svømmer ud af akvariet, derfor sætter vi positionen til kanten af akvariet, hvis den er kommet udenfor, hvilket får det til at se ud som om fisken er svømmet ind i kanten. For hver frame nulstilles accelerationen så, inden vi udregner det hele igen. 4.2 Finner og Haler Både hajen og fiskene har finner og haler der bevæger sig, således at det ser ud som om de svømmer. Bevægelsen er givet ved en rotation hvor vinklen er styret af en cosinusfunktion af tiden. Vinklen bliver ganget med længden af den nuværende hastighedsvektor, således at bevægelsen bliver større jo hurtigere fisken eller hajen bevæger sig. 4.3 Tang Vi har tilføjet tang til akvariebunden. Tangen bliver fordelt tilfældigt når akvarieklassen bliver initialiseret. Højden tangen skal stå i er givet ved højden af bunden i den tilsvarende position. Tangen består af en firkant med en tekstur på. De to øverste knuder i firkanten bliver for hver frame forskudt synkront fra side til side i forhold til en sinusfunktion af tiden, hvilket giver en effekt af at det svajer i strømmen. Texturen til tanget er givet ved et r/g/b billede med selve texturen, samt et sort/hvidt billede der angiver alphaværdien. Således er texturen ved hjælp af blending gjort gennemsigtig der hvor der ikke er tang. For at de gennemsigtige dele af tangen bliver tegnet ordentligt sørger vi for at tegne tangen som det sidste i scenen. Desuden er vi nødt til for hver frame at sortere tangen i forhold til afstand fra synspunktet, således at de fjerneste tang bliver tegnet først. Gør vi ikke dette vil de gennemsigtige dele af tang-firkanten skygge over bagvedliggende tang der bliver tegnet senere. For at få tangen til at se tilfældig ud roterer vi hver tang-stykke tilfældigt, og hver stykke tang får lagt en tilfældigt valgt konstant værdi til tiden inden sinus udregnes, så alt tang ikke svajer i takt, hvilket ser unaturligt ud. 4.4 Haj På samme måde som fiskene bliver hajen også styret ud fra en accelerationsvektor. Når hajen ikke styres direkte accelererer den blot væk fra siderne når den kommer for tæt på. Man kan vælge at overtage styringen ved hjælp af tastaturet. Det foregår ved at vi for hver frame spørger keyhandler klassen om hvilke taster der er trykket ned. Man kan så styre den på tre måder. Den kan accelerere eller bremse. Dette foregår bare ved at accelerationen bliver sat i henholdsvis modsat eller samme retning som den nuværende hastighed. Man kan dreje til højre eller venstre. Vi udregner retningen der skal drejes i som krydsproduktet af den nuværende hastighedsvektor og en vektor der peger op, altså i retning af y-aksen. Hajen accelererer så i den retning eller den modsatte alt efter om der skal drejes til højre eller venstre. Accelerationen bliver ganget med længden af hastigheden, så hajen drejer hurtigere når den svømmer hurtigere. 5
Figur 2: Dette er den oprindelige pyramide, som har origo midt på bunden. Vi bruger den til at tegne fiskenes hoveder og kroppe. Der kan svømmes op eller ned. Dette er blot en acceleration i y-aksens retning, positiv eller negativ. Den bliver tilsvarende ganget med længden af hastighedsvektoren. 4.5 Synsfelt Vi har en grabber der styrer hvorfra vi ser scenen. Vi flytter altså ikke på kameraet undervejs, men flytter og drejer scenen alt efter hvorfra vi ser. Dette står grabberen for. Som default ser vi bare hele akvariet fra en væg mod den modsatte væg. Man kan så vælge at se det fra en fisk eller hajens perspektiv. For at kunne gøre det får grabberen som input en position og en retningsvektor. Da vi bruger y-aksen som op, projicerer vi retningsvektoren ned på xz-planet. Så kan vi udregne vinklen vi skal rotere om y-aksen for at se i den rigtige retning i forhold til xz-planet. Dog skal vi også kigge op eller ned alt efter retningsvektoren, så vi udregner en akse vi kan rotere om som krydsproduktet mellem hastighedsvektoren og projiceringen på xz-planet, og udregner vinklen der skal roteres. 5 Geometrierne 5.1 Fisk Fisken består af 5 pyramider, der igen består af 4 trekanter og en firkant. Pyramiderne der udgør hoved og krop er de af Peter Møller udleverede pyramider. Finner og hale er lavet med en anden pyramide, da vi havde brug for at de kunne rotere om spidsen. På Figur 2 ses den pyramide vi har fået udleveret, origo i koordinatsystemet er midt på bunden af pyramiden. Hvis vi skulle bruge denne pyramide til hale, kunne vi ikke få den til at svinge fra side til side som vi ville. Dette kunne ikke lade sig gøre da et 6
Figur 3: Denne pyramide har spidsen i origo i stedet for på midten i bunden. Vi kan således nemmere animere den til at vifte frem og tilbage om spidsen. glrotatef på denne pyramide ville gøre at den roterede omkring en akse ved bunden af pyramiden. Derfor lavede vi pyramiden som ses i Figur 3. Her ligger pyramidens koordinatsytems origo ved spidesen af pyramiden, og derfor er det nemt at rotere om denne. Tidligere har vi ikke beskrevet ret mange transformationer. De fleste transformationer foregår under tegningen af fisken, når vi kalder fish::draw(). Kroppen bliver lavet først, der bliver tegnet en almindelig pyramide, den bliver skaleret og roteret så den vender rigtigt. Herefter tegnes hovedet. Dette er også en almindelig pyramide der bliver skaleret og roteret. Halen tegnes derefter, den består af en pyramide med spidsen i origo, den bliver skaleret, der bliver tilknyttet en tumbler (for at få den til at vifte) samt den bliver transformeret til enden af kroppen. Finnerne tegnes til sidst, de er ligesom halen tegnet med en pyramide med origo i spidsen, de bliver skaleret, roteret, tilknyttet tumbler og flyttet ud på siden af fisken. Efter fisken er tegnet skal vi have den roteret så den vender i retningen med speedvektoren, og så den har hovedet opad. Dette gør vi ved hjælp af vektorregning og glrotatef. 5.2 Haj Hajen tegnes på samme måde som en fisk, den er blot skaleret så den er større, og den har fået monteret en rygfinne. Rygfinnen er en pyramide med origo i spidsen. Finnen bliver scaleret så den er stor nok til at kunne ses, og se skræmmende ud som rygfinne. Herefter bliver finnen bliver roteret om x- og y-aksen så spidsen vender opdad, og flyttet så den sidder på ryggen af hajen. Hele hajen bliver scaleret så den bliver større end fiskene, og bliver ligesom fiskene roteret så den har rygfinnen opad, og næsen i retning mod hastigheden. 7
Figur 4: Således ser fisken ud efter alle delelementerne er sat sammen. 5.3 Akvarium Akvariet er bygget af 4 firkanter der udgør siderne. Toppen og bunden er lavet af en texturedoscsurface. Inden de bliver tegnet bliver de roteret om x-aksen til at ligge i xz-planen og skaleret så de har længde og bredde som akvariet. Selvom de begge bliver tegnet som texturedoscsurface er det kun toppen der bliver animeret, via. preframe, bunden er fast. I akvariet er der også tang. Tangen er tegnet som en firkant, hvorpå der er en tangtextur. Derfor ser tangen flad ud når man ser den fra siden. A Sådan køres projektet Hele projektet er i mappen cgf. Projektet compiles ved at taste make når man står i cgf/. Projektet køres ved at taste main. Når projektet kører er det muligt at skifte kameraposition ved at trykke på f-tasten. Der skiftes mellem tre kameraer: kamera bag en fisk i flokken, kamera bag hajen og default kamera der giver overblik over størstedelen af akvariet. For at styre hajen trykkes først på c-tasten. Her efter kan den drejes til højre og venstre med piletasterne, og op og ned på henholdsvis l-tasten og.-tasten. Det er muligt at sætte farten op eller ned, dette gøres med henholdsvis pil-op-tasten og pil-ned-tasten. Man kan flytte synsfeltet til at være bag hajen ved at trykke på f. Hvis der trykkes på c-tasten igen svømmer hajen selv rundt. 8