Praktikum BKSPP Aufgabenblatt Nr. 3 - Goethe

Werbung
PD Dr. David Sabel
Institut für Informatik
Fachbereich Informatik und Mathematik
Johann Wolfgang Goethe-Universität Frankfurt am Main
Praktikum BKSPP
Wintersemester 2014/15
Aufgabenblatt Nr. 3
Abgabe: Dienstag, 16. Dezember 2014
Auf diesem Aufgabenblatt wird OpenGL1 verwendet, um graphische (2D und 3D) Ausgaben zu erzeugen. Als Haskell-Bibliotheken zur Anbindung an OpenGL und GLUT2
werden die entsprechenden Cabal-Pakete
• OpenGL (http://hackage.haskell.org/package/OpenGL) und
• GLUT (http://hackage.haskell.org/package/GLUT)
verwendet.
Die Haskell-Platform beinhaltet diese Bibliotheken bereits. Eventuell muss auf dem eigenen Rechner die GLUT Bibliothek nachinstalliert werden, z.B.:
• FreeGLUT unter Linux http://freeglut.sourceforge.net/ oder
• GLUT for Windows (http://www.xmission.com/∼nate/glut.html)
Es sollte ausreichen die Zip-Datei unter http://user.xmission.com/∼nate/glut/glut-3.7.6-bin.zip
herunterzuladen und die Datei glut32.dll in das Verzeichnis %WinDir%\System zu kopieren.
Testen auf verschiedenen System ergab, dass das Starten eines OpenGL-Fenster aus dem
ghci heraus manchmal zu Abstürzen führt. In diesem Fall empfiehlt es sich das Programm
zu kompilieren. Im Allgemeinen sollte ein Aufruf der Form
ghc --make -o output.exe Input.hs
wobei Input.hs das Programmfile ist, funktionieren. Anschließend kann das Programm
output.exe ausgeführt werden.
Als erster Einstieg zur OpenGL-Programmierung mit Haskell kann die Seite
http://www.haskell.org/haskellwiki/Opengl dienen, über die z.B. das OpenGL Tutorial 1 (http://www.haskell.org/haskellwiki/OpenGLTutorial1) und OpenGL Tutorial 2
(http://www.haskell.org/haskellwiki/OpenGLTutorial2) zu finden sind.
Eine sehr gute Einführung bietet das Tutorial von Sven Eric Panitz, welches über
http://www.cs.hs-rm.de/∼panitz/hopengl frei verfügbar ist.
1
2
OpenGL steht für Open Graphics Library, die Webseite von OpenGL ist http://www.opengl.org/
GLUT steht für OpenGL Utility Toolkit (siehe auch http://www.opengl.org/resources/libraries/glut/
1
1
Grundgerüst für ein OpenGL-Programm in Haskell
Wir betrachten ein einfaches Grundgerüst für ein in Haskell implementiertes OpenGLProgramm. Zunächst werden die beiden Bibliotheken für OpenGL und für GLUT importiert:
import Graphics.UI.GLUT
import Graphics.Rendering.OpenGL
Das Hauptprogramm erhält folgendes Gerüst
main = do
(prgName,_) <- getArgsAndInitialize
createWindow prgName
windowSize $= Size 800 800
displayCallback $= mainDisplay
mainLoop
Hierbei wird in der ersten monadischen Aktion getArgsAndInitialize GLUT initialisiert und an prgName der Aufruf-Name des Programms gebunden. Im zweiten Schritt
wird mittels createWindow prgName ein Fenster mit dem Titel des Programmnamens erzeugt. In Zeile 3 wird mit dem Operator $= der OpenGL-Variablen windowSize die Größe
800x800 zugewiesen, d.h. das Fenster wird auf diese Größe eingestellt. In Zeile 4 wird mit
displayCallback $= mainDisplay der OpenGL-Variablen displayCallback die Funktion mainDisplay zugewiesen (diese wird später definiert). Hierbei ist displayCallback ein
(eingebauter) Ereignis-Handler, der das Ereignis des Anzeigens des Fensters übernimmt.
Durch unsere Zuweisung wird bei jedem Anzeigen und Wiederanzeigen des Fensters, die
mainDisplay-Funktion aufgerufen, sie bestimmt also den Fensterinhalt. Schließlich wird
in der letzten Zeile mit mainLoop der GLUT-Ereignisprozesser gestartet, der sämtliche Ereignisse abfängt und an die vorher definierten Handler übergibt (in diesem Fall wurde
nur der displayCallback-Handler vorher registriert).
Es fehlt nun noch die Implementierung der Funktion mainDisplay, die den Fensterinhalt
beschreibt. Eine ganz einfache Definition ist:
mainDisplay = clear [ColorBuffer]
In diesem Fall wird der Fensterinhalt gelöscht. Ruft man das Programm insgesamt auf,
erhält man ein schwarzes Fenster.
Wir betrachten nun einige einfache Funktionen zum Anzeigen von Punkten, Linien,
Streckenzügen und Polygonen. Wir definieren hierfür zunächst zwei Typsynonyme
type Coord = (GLfloat,GLfloat,GLfloat)
type ColoredCoord = (GLfloat,GLfloat,GLfloat,Color4 GLfloat)
2
Der Typ Coord stellt ein 3-Tupel von GLfloat-Werten dar. Seine Bedeutung ist eine 3dimensionale Koordinate. Jeder der einzelnen drei Werte sollte hierbei einen Wert zwischen -1 und 1 besitzen, damit er innerhalb des Fensters liegt. Der Typ ColoredCoord ist
ein 4-Tupel, wobei die ersten 3 Komponenten wieder X-, Y-, und Z-Koordinaten sind und
der vierte Wert eine Farbe darstellt. Für die Farben benutzen wir den vordefinierten Typ
Color4 GLfloat, der RGBA-Farbwerte darstellen kann. RGBA steht für Red-Green-BlueAlpha, mit dem Datenkonstruktor Color4 können Farben erzeugt werden, z.B.
red
= Color4 1 0 0 1
green = Color4 0 1 0 1
blue = Color4 0 0 1 1
Alle 4 Werte sollten zwischen 0 und 1 liegen. Die ersten 3 Werte geben den Rot-, Grün,
bzw. Blau-Anteil an, der letzte Wert (der Alpha-Wert) die Transparenz der Farbe.
Eine Funktion zum Anzeigen einer Liste von Punkten (vom Typ Coord) kann in Haskell
nun programmiert werden als
displayPoints points =
renderPrimitive Points $ mapM_ (\(x, y, z)-> vertex $ Vertex3 x y z) points
Die wesentliche Bibliotheksfunktion ist hierbei renderPrimitive, die einfache Grafikobjekte erzeugen kann. Mit vertex $ Vertex3 x y z werden die Punkte vom Typ Coord
noch in für renderPrimitive passende Objekte (dies sind monadische IO-Aktionen) konvertiert.
Eine erweiterte Variante ist die folgende Funktion, die farbige Punkte (vom Typ
ColoredCord) anzeigen kann:
displayCPoints points =
renderPrimitive Points $ mapM_ (\(x, y, z, c) -> do
currentColor $= c
vertex $ Vertex3 x y z) points
Der Aufbau gleicht fast displayPoints, mit der Ausnahme, dass für jeden Punkt eine
erweiterte IO-Aktion erzeugt wird: Bevor der eigentliche Punkt mit vertex ... erzeugt
wird, wird die aktuelle Farbe mit currentColor $= c auf den entsprechenden Farbwert
gesetzt.
Eine Funktion zur Anzeige von Linien kann programmiert werden als
displayLines points =
renderPrimitive Lines $ mapM_ (\(x, y, z)-> vertex $ Vertex3 x y z) points
Hierbei werden je zwei Punkte aus der Liste der übergebenen Punkte als Endpunkte einer
Linie verwendet.
Die Funktion
3
displayLineStrip points =
renderPrimitive LineStrip $ mapM_ (\(x, y, z)-> vertex $ Vertex3 x y z) points
zeichnet einen Streckenzug durch die übergebenen Koordinaten.
Ein (konvexes) farbiges Polygon (gegeben durch Koordinaten und eine Farbe) kann gezeichnet werden mit
displayPolygon points color =
do
currentColor $= color
renderPrimitive Polygon $ mapM_ (\(x, y, z)-> vertex $ Vertex3 x y z) points
Wir können die mainDisplay-Funktion z.B. wie folgt abändern, um verschiedene Objekte
zu zeichnen. Hierbei sollte am Ende ein Aufruf von flush erfolgen, um alle Zwischenbuffer sicher zu leeren, und damit zu garantieren, dass wirklich alle Objekte gezeichnet
werden.
mainDisplay = do
-- Hintergrundfarbe festlegen
clearColor $= Color4 1 1 1 1
-- Fensterinhalt l"oschen
clear [ColorBuffer]
-- Farbe auf Schwarz setzen
currentColor $= Color4 0 0 0 1
-- Punkte anzeigen
displayPoints beispielPunkte
-- farbige Punkte anzeigen
displayCPoints beispielFarbpunkte
-- rotes Polygon anzeigen
displayPolygon beispielPunkte red
-- aktuelle Farbe auf gr"un umstellen
currentColor $= green
-- Linien zeichnen
displayLines beispielPunkte2
currentColor $= blue
-- Linien zeichnen
displayLineStrip beispielPunkte3
flush
beispielPunkte :: [Coord]
beispielPunkte =
[(-0.25, 0.25, 0.0), (0.75, 0.35, 0.0), (0.75, -0.15, 0.0), (-0.75, -0.25, 0.0)]
4
beispielPunkte2 :: [Coord]
beispielPunkte2 =
[(-0.3, 0.1, 0.0), (0.8, 0.1, 0.0), (0.5, -0.7, 0.0), (-0.4, -0.1, 0.0)]
beispielPunkte3 :: [Coord]
beispielPunkte3 =
[(-0.3, 0.1, 0.0), (-0.1, 0.1, 0.0), (0.3, 0.4, 0.0), (0.8, 0.1, 0.0),
(0.5, 0.7, 0.0), (0.4, -0.1, 0.0)]
beispielFarbpunkte :: [ColoredCoord]
beispielFarbpunkte =
[(-0.5, 0.5, 0.0, red), (0.5, -0.5, 0.0,green), (-0.5, -0.5, 0.0,blue),
(0.5, 0.5, 0.0,Color4 1 1 1 1)]
Ausführung des Programms ergibt das folgende Fenster
5
Aufgabe 1 Implementieren Sie in Haskell ein OpenGL-Programm zum Zeichnen von Funktionsgraphen. Dabei sollte eine Funktion
plot :: GLfloat -> (GLfloat -> GLfloat) -> GLfloat -> GLfloat -> [Coord]
plot delta f start stop = ...
erstellt werden, wobei delta eine Schrittweite, f eine Funktion auf GLfloat-Werten, und start
und stop das zu betrachtende Intervall (X-Werte) markieren. plot berechnet die Koordinaten des
Funktionsgraphen im Intervall [start,stop] mit einer Schrittweite von delta.
Implementieren Sie anschließend eine Funktion bestScale :: [Coord] -> [Coord], die eine
Liste von Koordinaten erhält und diese so skaliert, dass alle Punkte im Fenster liegen, d.h. X,Y,ZKoordinaten der Ausgabe Koordinaten liegen im Intervall [−1, 1].
Kombinieren Sie plot und bestScale und lassen sie verschiedene Funktiongraphen zeichnen.
Als Beispiel betrachten wir die Exponentialfunktion im Bereich -5 bis 10:
test
= let delta = 0.01
start = -5
stop = 10
f x
= 2**x
in plot delta f start stop
Die folgenden zwei Abbildungen zeigen links das Ergebnis ohne Skalierung und rechts die Ausgabe
bei Verwendung von bestScale:
6
2
Zweidimensionale Turtlegrafiken
Im Folgenden verwenden wir Haskells OpenGL-Anbindung, um zweidimensionale
Turtle-Grafiken zu erzeugen. Wir implementieren hierfür keine eigene Turtle-Sprache,
sondern betten diese Sprache als Datentyp in Haskell ein. Der Zustand einer (zweidimensionalen) Schildkröte (Turtle) besitzt die folgenden Attribute:
• Position im zweidimensionalen Raum als X- und Y-Koordinaten
• einen Winkel, der die Laufrichtung der Schildkröte angibt (im Gradmaß)
• Ein Flag, das angibt, ob die Schildkröte beim Laufen zeichnet oder nicht zeichnet.
• Eine Farbe, die angibt, mit welcher Farbe die Schildkröte zeichnet.
In Haskell stellen wir eine Schildkröte durch den Datentyp Turtle dar, wobei wir die
Record-Syntax verwenden:
data Turtle = Turtle
{ xpos
:: GLfloat,
ypos
:: GLfloat,
angle
:: GLfloat,
painting :: Bool
tcolor
:: Color4 GLfloat
}
Eine Schildkröte kann die folgenden Kommandos verarbeiten:
• Gehen: Die Schildkröte bewegt sich um einen übergebenen Wert vorwärts (in Richtung des eingestellten Winkels), ist der übergebene Wert negativ, so läuft die Schildkröte rückwärts. Ist painting auf True gesetzt, so zeichnet die Schildkröte den gegangenen Weg.
• Drehen: Die Schildkröte dreht sich im Uhrzeigersinn, um den übergebenen Winkel.
Ist der Winkel negativ, so dreht sich die Schildkröte gegen den Uhrzeigersinn.
• Stift an/aus: Das Flag (Zeichnen ja/nein) wird geändert.
• Farbe setzen: Der Wert von tcolor wird auf einen übergebenen Farbwert gesetzt.
In Haskell stellen wir Schildkröten-Aktionen durch einen Datentypen dar:
data TurtleAction =
Move GLfloat
| Turn GLfloat
| FlipPaint
| SetColor (Color4 GLfloat)
7
Aufgabe 2 Implementieren Sie eine Funktion
applyAction :: TurtleAction -> Turtle -> Turtle
die eine Schildkrötenaktion und eine Schildkröte erwartet und den Nachfolgezustand der Schildkröte berechnet.
Hinweise:
• Beachten Sie, dass die Gehen-Aktion die Weglänge erhält. Um daraus entsprechende X- und
Y -Koordinaten zu berechnen, sollten sie die in Haskell eingebauten trigonometrischen Funktionen sin und cos verwenden, wobei sie den Winkel hierfür vom Gradmaß ins Bogenmaß
umrechnen müssen. Hierfür gilt die Formel3 :
winkelImGradmaß = (winkelImBogenmaß/π) · 180
• Für die Berechnung der Koordinaten könnte folgendes Bild hilfreich sein:
c
sin(α) = a/c
cos(α) = b/c
a
•
α
b
Ein Schildkrötenprogramm ist eine Sequenz von Schildkrötenaktionen. In Haskell kann
ein solches Programm durch folgenden Typ dargestellt werden:
type TurtleProgram = [TurtleAction]
Aufgabe 3 Implementieren Sie in Haskell ein Programm, dass eine initiale Schildkröte und ein
Schildkrötenprogramm erwartet, und die von der Schildkröte bei Ausführung des Schildkrötenprogramms erzeugte Grafik mittels OpenGL darstellt.
Hinweise:
• Im Grunde müssen Sie nur die Linien berechnen und darstellen, die durch jene MoveOperationen erzeugt werden, bei denen der Stift an ist.
• Es könnte durchaus hilfreich sein, zunächst eine Funktion zum Darstellen farbiger Linien
(die evtl. durch ein neues Typsynonym dargestellt werden) zu schreiben (als Vorlage können
dafür die Funktionen displayCPoints und displayLines dienen).
• Sie können die Funktion applyAction aus der vorherigen Aufgabe verwenden.
3
Eine Näherung der Zahl π ist in Haskell durch die Funktion pi bereits vordefiniert.
8
Aufgabe 4 Implementieren Sie in Haskell ein Schildkrötenprogram (vom Typ TurtleProgram),
welches das folgende „Haus vom Nikolaus“ ohne Ausschalten des Stifts zeichnet:
Aufgabe 5 Implementieren Sie in Haskell eine rekursive Funktion nEck, die eine Zahl n erwartet
und ein Schildkrötenprogram (vom Typ TurtleProgram) generiert, welches ein gleichmäßiges nEck zeichnet. Die folgenden Abbildungen zeigen Aufrufe für n = 8 und n = 15:
Aufgabe 6 Implementieren Sie in Haskell eine rekursive Funktion nnEck, die eine Zahl n erwartet
und ein Schildkrötenprogram (vom Typ TurtleProgram) generiert, das n viele n-Ecke im Kreis
herum zeichnet. D.h. vom Mittelpunkt aus: Nach Zeichnen eines n-Ecks wird das nächste n-Eck
um (360/n) Grad gedreht gezeichnet. Die folgenden Abbildungen zeigen Aufrufe für n = 3, n = 8
und n = 20, wobei die Größe der einzelnen n-Ecke angepasst wurde.
9
Aufgabe 7 Implementieren Sie in Haskell eine rekursive Funktion pBaum, die ein Schildkrötenprogram (vom Typ TurtleProgram) generiert, das einen so genannten Pythagorasbaum zeichnet.
Die Konstruktion (siehe linkes Bild unten) beginnt hierbei mit einem Quadrat, auf welches in
einem rechtwinkligen Dreieck rekursiv zwei Dreiecke gezeichnet werden, z.B. für die Winkel 30 und
60 Grad. Führt man diese Konstruktion rekursiv weiter bis die Quadrate ziemlich klein werden,
erhält man den Pythagorasbaum wie im mittleren Bild gezeigt. Schöner wird der Baum, wenn man
nur die linke und die rechte Seite jedes Quadrats zeichnet und noch eine Färbung der Quadrate
vornimmt (rechtes Bild).
10
3
Dreidimensionale Turtlegrafiken
In diesem Abschnitt wird das bisherige Turtle-Interface auf drei Dimensionen erweitert.
Neben den bereits aus dem Zweidimensionalen bekannten Attributen, erhält eine dreidimensionale Schildkröte die folgenden zusätzlichen Eigenschaften:
• Für die Position im dreidimensionalen Raum zusätzlich zu den X- und YKoordinaten eine Z-Koordinate.
• Der bisherige Winkel gibt eine Laufrichtung bzgl. der X- und Y-Achse an. Nun wird
ein weiterer Winkel hinzugefügt, der die Laufrichtung bezüglich der X- und ZAchse angibt. Dies entspricht im Wesentlichen so genannten Kugelkoordinaten.
Unser Haskell-Datentyp wird dementsprechend erweitert in:
data Turtle = Turtle
{ xpos
:: GLfloat,
ypos
:: GLfloat,
angle
:: GLfloat,
painting :: Bool,
tcolor
:: Color4 GLfloat,
-- neu:
zpos
:: GLfloat,
angleXZ :: GLfloat
}
Damit sich die Schildkröte im 3-dimensionalen-Raum bewegen kann, wird als neue
Aktion das Drehen des Winkels angleXZ hinzugefügt. Die Interpretation der GehenOperation ändert sich natürlich auch, da beide Winkel beachtet werden müssen.
Das folgende Bild zeigt die Kugelkoordinaten, wobei α der Winkel zwischen der X- und
der Y-Achse ist und β der Winkel zwischen der X-Y-Ebene und der Z-Achse ist.
11
z
β
α
y
x
Sei r die Länge des blauen Pfeils. Dann kann man die Längen der Projektionen auf die
einzelnen Achsen wie folgt berechnen (diese benötigen sie für die Implementierung der
Gehen-Operation!):
x = r · cos α · sin β
y = r · sin α · sin β
z = r · cos(β)
Aufgabe 8 Implementieren Sie in Haskell ein Modul Turtle3D, welches das zweidimensionale Schildkröteninterface auf drei Dimensionen erweitert und Schildkrötenprogramme ausführen
(und korrekt darstellen) kann. Schildkrötenprogramme sind hierbei Listen von Elementen des Typs
TurtleAction, der um die TurnXZ-Aktion erweitert wurde, d.h.
data TurtleAction =
Move GLfloat
| Turn GLfloat
| FlipPaint
| SetColor (Color4 GLfloat)
| TurnXZ GLfloat
Hinweise:
Im CVS-Repository ist ein erweitertes Basisprogramm namens Basis3D.hs nebst zwei Hilfsmodulen (aus der Anleitung von S.E. Panitz) eingecheckt. Dieses erlaubt Interaktion: Durch drücken
der Pfeiltasten kann navigiert, durch Drücken von + und - gezoomt werden. Verwenden Sie dieses
Programm als Rahmenprogramm für das Schildkrötenprogramm.
Aufgabe 9 Implementieren Sie in Haskell eine Funktion
wuerfel :: GLfloat -> [TurtleProgram]
12
die eine Länge erwartet und ein Schildkrötenprogramm zur Erzeugung eines dreidimensionalen
Würfels erzeugt. Eine Ausgabe zeigt folgendes Bild:
Aufgabe 10 Implementieren Sie in Haskell eine Funktion pyramide, die eine Zahl w erwartet und ein Schildkrötenprogramm erzeugt, welches eine Stufenpyramide zusammengesetzt aus
Würfeln zeichnet, wobei die Grundfläche aus w × w Würfeln besteht.
Das linke Bild zeigt eine Pyramide für w = 5. Das rechte Bild eine Pyramide für w = 25, wobei
die einzelnen Ebenen in verschiedenen Farben gezeichnet wurden:
Aufgabe 11 Impementieren Sie in Haskell eine Funktion meinhaus, welches ein Schildkrötenprogramm generiert, dass ein Haus mit möglichst vielen Details zeichnet.
Das schönste Haus wird prämiert!
13
Herunterladen