JOGL Java Binding for the OpenGL API Ausarbeitung im Rahmen der Lehrveranstaltung “Graphisch Interaktive Systeme” (WS 08/09) erstellt von Michael Kremer Email: [email protected] JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 Inhalt 1. Einleitung 3 2. Über JOGL 4 2.1 Aufbau von JOGL 5 2.2 Die wichtigsten Klassen 7 Installation 9 3. 3.1 4. Screenshots zur Einrichtung 10 Tutorial in Eclipse 11 4.1 Ein neues Projekt anlegen 11 4.2 Ein Swing-Fenster erzeugen 12 4.3 Einen GLEventListener implementieren 14 4.4 Ein Koordinatensystem zeichnen 15 4.5 Tastatur-Ereignisse verarbeiten 18 4.6 Maus-Ereignisse verarbeiten 20 4.7 Animation hinzufügen 21 5. Eigenes Beispiel: ASURO-Roboter 24 6. Fazit 25 7. Quellen 25 Seite 2 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 1. Einleitung Die Programmiersprache Java erfreut sich immer größerer Beliebtheit. Dank der Objektorientierung und der Plattformunabhängigkeit sowie der großen Anzahl an vielseitigen Erweiterungen ist sie für beinahe jede Problemstellung geeignet. Vor allem die komfortablen Entwicklungswerkzeuge wie Eclipse oder die Netbeans IDE, die kostenlos erhältlich sind und ständig von einer großen Benutzergemeinde weiterentwickelt werden, machen diese Sprache für Entwickler interessant. Dies galt allerdings lange Zeit nicht für die Spieleentwicklung, denn hier ist OpenGL der Standard in 2D- und 3D-Applikationen. Zwar hat man versucht, mit Java3D eine Alternative zu OpenGL zu etablieren, allerdings wurde die Weiterentwicklung aufgrund der geringen Performance eingestellt. An dieser Stelle tritt JOGL, das „Java Binding for the OpenGL API“, auf den Plan. Neben anderen Bindings wie der „Lightweight Java Game Library“ (LWJGL) oder „gl4java“ schlägt auch JOGL eine Brücke zwischen Java und den OpenGLFunktionalitäten und ermöglicht es, auch unter Java komplexe 2D- und 3D-Grafiken zu realisieren. Dies ist vor allem für Internet-Anwendungen interessant, da die Applikationen als Applets auf allen gängigen Betriebsystemen mit JavaUnterstützung ausgeführt werden können. Diese Ausarbeitung soll JOGL vorstellen und hauptsächlich eine Einführung in die Benutzung geben. Nach einer kurzen Beschreibung wird zunächst die Installation und dann in einem einfachen Tutorial die Benutzung von JOGL erklärt. Mit diesen Kenntnissen kann ein Entwickler, der seine OpenGL-Applikationen bisher nur in C oder C++ geschrieben hat, seine Ideen sofort auf Java übertragen und dessen Vorzüge nutzen. Außerdem soll dabei die große Ähnlichkeit zu der bisher gewohnten Benutzung von OpenGL deutlich gemacht werden. Als Beispiel für komplexere Anwendungen habe ich eine 3D-Simulation in Java realisiert, die einen animierten ASURO-Roboter darstellt, der einer Linie auf dem Fußboden hinterher fährt und dabei aus allen Richtungen betrachtet werden kann. Dazu aber später mehr. Seite 3 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 2. Über JOGL JOGL ist ein Open-Source Projekt (Homepage: http://jogl.dev.java.net), das seit 2003 kontinuierlich weiterentwickelt wird. Da die Entwicklung von Sun Microsystems auf der Java-Seite und Silicon Graphics Incorporated auf der OpenGL-Seite unterstützt wird, ist eine hohe Qualität und Aktualität des Bindings gewährleistet. Probleme und Fragen können in eigenen Foren mit einer großen Benutzergemeinde diskutiert und gelöst werden. Um die Lücke zwischen Java und dem C-Code von OpenGL zu schließen macht JOGL sich das Java-Native-Interface, kurz JNI, zu Nutze. Das JNI ermöglicht es, aus Java-Anwendungen heraus Code auszuführen, der in anderen Programmiersprachen geschrieben wurde und auf die jeweilige Plattform angepasst ist. Deshalb gibt es unterschiedliche Versionen von JOGL, die auf die entsprechenden Betriebssysteme ausgelegt sind. Unterstützt werden Windows, Linux, Solaris und MacOS. JOGL besteht im Wesentlichen aus Java Klassen und Schnittstellen, die auf den originalen C-Code von OpenGL zurückgreifen. Der größte Teil des C-Codes wird dabei individuell von dem in JOGL enthaltenen Tool GlueGen während der Kompilierung erzeugt. Der restliche C-Code dient hauptsächlich dazu, Plattformabhängige Schwierigkeiten zu umschiffen. Die aktuelle JOGL-Version 1.1.2 unterstützt OpenGL in der Spezifikation 2.1. Man findet fast alle Funktionen die man von OpenGL gewohnt ist wieder. Dazu zählen auch die Funktionen der GLU-Bibliothek (OpenGL Utility Library) und der GLUTBibliothek (OpenGL Utility Toolkit). Weggefallen sind Funktionalitäten für das Fenster- bzw. Menü-Management, da diese in Java ja bereits durch Swing und AWT bereitgestellt werden und auch in einem JOGL Programm beliebig benutzt werden können. Weiterhin fehlen Callback-Funktionen für Maus, Tastatur und andere Geräte. Diese werden in Java durch die entsprechenden EventListener ersetzt. Diese Bereiche werden später im Tutorial erneut angesprochen und praktisch umgesetzt. Im Übrigen hat sich seit der letzten Version 1.1.1 der Name des Projekts geändert. Es hieß vorher „Java Bindings for OpenGL“. Seite 4 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 2.1 Aufbau der JOGL-Library Die Klassen-Hierarchie: o class java.lang.Object o class com.sun.opengl.util.Animator o class com.sun.opengl.util.FPSAnimator o class javax.media.opengl.AWTGraphicsConfiguration (implements javax.media.opengl.AbstractGraphicsConfiguration) o class javax.media.opengl.AWTGraphicsDevice (implements javax.media.opengl.AbstractGraphicsDevice) o class com.sun.opengl.util.BufferUtil o class com.sun.opengl.cg.CGannotation o class com.sun.opengl.cg.CGcontext o class com.sun.opengl.cg.CGeffect o class com.sun.opengl.cg.CgGL o class com.sun.opengl.cg.CGparameter o class com.sun.opengl.cg.CGpass o class com.sun.opengl.cg.CGprogram o class com.sun.opengl.cg.CGstate o class com.sun.opengl.cg.CGstateassignment o class com.sun.opengl.cg.CGtechnique o class java.awt.Component (implements java.awt.image.ImageObserver, java.awt.MenuContainer, java.io.Serializable) o class java.awt.Canvas (implements javax.accessibility.Accessible) o class javax.media.opengl.GLCanvas (implements javax.media.opengl.GLAutoDrawable) o class java.awt.Container o class javax.swing.JComponent (implements java.io.Serializable) o class javax.swing.JPanel (implements javax.accessibility.Accessible) o class javax.media.opengl.GLJPanel (implements javax.media.opengl.GLAutoDrawable) o class java.awt.Panel (implements javax.accessibility.Accessible) o class java.applet.Applet o class com.sun.opengl.util.JOGLAppletLauncher o class com.sun.opengl.util.texture.spi.DDSImage o class com.sun.opengl.util.texture.spi.DDSImage.ImageInfo o class javax.media.opengl.DebugGL (implements javax.media.opengl.GL) o class javax.media.opengl.DefaultGLCapabilitiesChooser (implements javax.media.opengl.GLCapabilitiesChooser) o class com.sun.opengl.util.FileUtil o class com.sun.opengl.util.Gamma o class javax.media.opengl.GLCapabilities (implements java.lang.Cloneable) o class javax.media.opengl.GLContext o class javax.media.opengl.GLDrawableFactory o class javax.media.opengl.glu.GLU o class com.sun.opengl.util.GLUT o class javax.media.opengl.glu.GLUtessellatorCallbackAdapter (implements javax.media.opengl.glu.GLUtessellatorCallback) o class com.sun.opengl.util.ImageUtil o class com.sun.opengl.util.j2d.Overlay o class com.sun.opengl.util.Screenshot o class com.sun.opengl.util.texture.spi.SGIImage o class com.sun.opengl.util.StreamUtil Seite 5 JOGL Java Binding for the OpenGL API o o o o o o o o o o o o o o Michael Kremer WS 2008/2009 class com.sun.opengl.util.j2d.TextRenderer class com.sun.opengl.util.j2d.TextRenderer.DefaultRenderDelegate (implements com.sun.opengl.util.j2d.TextRenderer.RenderDelegate) class com.sun.opengl.util.texture.Texture class com.sun.opengl.util.texture.TextureCoords class com.sun.opengl.util.texture.TextureData class com.sun.opengl.util.texture.TextureIO class com.sun.opengl.util.j2d.TextureRenderer class com.sun.opengl.util.texture.spi.TGAImage class com.sun.opengl.util.texture.spi.TGAImage.Header class com.sun.opengl.util.TGAWriter class javax.media.opengl.Threading class java.lang.Throwable (implements java.io.Serializable) o class java.lang.Exception o class java.lang.RuntimeException o class com.sun.opengl.cg.CgException o class javax.media.opengl.GLException class com.sun.opengl.util.TileRenderer class javax.media.opengl.TraceGL (implements javax.media.opengl.GL) Die Schnittstellen-Hierarchie: o o o o o o o o o o o o o o o interface javax.media.opengl.AbstractGraphicsConfiguration interface javax.media.opengl.AbstractGraphicsDevice interface javax.media.opengl.ComponentEvents o interface javax.media.opengl.GLAutoDrawable (also extends javax.media.opengl.GLDrawable) o interface javax.media.opengl.GLPbuffer interface java.util.EventListener o interface javax.media.opengl.GLEventListener interface javax.media.opengl.GL interface javax.media.opengl.GLCapabilitiesChooser interface javax.media.opengl.GLDrawable o interface javax.media.opengl.GLAutoDrawable (also extends javax.media.opengl.ComponentEvents) o interface javax.media.opengl.GLPbuffer interface javax.media.opengl.glu.GLUnurbs interface javax.media.opengl.glu.GLUquadric interface javax.media.opengl.glu.GLUtessellator interface javax.media.opengl.glu.GLUtessellatorCallback interface com.sun.opengl.util.j2d.TextRenderer.RenderDelegate interface com.sun.opengl.util.texture.TextureData.Flusher interface com.sun.opengl.util.texture.spi.TextureProvider interface com.sun.opengl.util.texture.spi.TextureWriter Wie man sieht ist JOGL relativ überschaubar und man entdeckt bereits unter den Klassen und Schnittstellen bekannte Namen wie GL, GLU und GLUT. Im folgenden Abschnitt werde ich die wichtigsten Klassen und Schnittstellen für ein einfaches Programm kurz vorstellen. Seite 6 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 2.2 Die wichtigsten Klassen und Schnittstellen: Die im folgenden beschriebenen Schnittstellen und Klasse reichen aus, um selbst OpenGL-Szenen unter JOGL zu erstellen. Sie werden alle später im Tutorial eingesetzt und dort ggf. noch näer erläutert. - Schnittstelle GLAutoDrawable: Stellt einen Event-basierten Kontext für das Rendering von Objekten bereit. Mit dieser Schnittstelle hat der Programmierer selbst nicht viel zu tun, da sie automatisch beim Einsatz eines GLEventListeners instanziiert wird und dort automatisch als Parameter übergeben wird. Dazu im Tutorial mehr. - Schnittstelle GLEventListener: Ein GLEventListener wird wie die bekannten Listener in Java an einen RenderKontext gebunden. Er beinhaltet die bekannten OpenGL-Funktionen „init“, „reshape“ und „display“ und zusätzlich die Funktion „displayChanged“, mit der man zum Beispiel auf Änderungen des Display-Modus des Bildschirms reagieren kann. Diese sogenannten Callback-Methoden werden genau wie aus einem CProgramm bekannt eingesetzt. - Schnittstelle GL: Diese Schnittstelle beinhaltet die Schnittstellen zu allen unterstützen OpenGL-CFunktionen. Eine fertige Instanz der Klasse erhält im man Programm stets von einem GLAutoDrawable. - Klasse Animator bzw. FPSAnimator: Stellt einen Thread bereit, der in bestimmten Abständen die Display-Funktion des Programms aufruft um Animationen zu erzeugen. Der normale Animator macht nur eine kurze Pause zwischen den Frames, dem FPSAnimator hingegen kann man eine Framerate vorgeben. Die Klasse Animator ist wie später gezeigt wird nicht immer geeignet. Seite 7 JOGL Java Binding for the OpenGL API - Michael Kremer WS 2008/2009 Klasse GLCanvas: Die Klasse ist Subklasse des normalen Canvas und stellt einen Rendering Kontext bereit, d.h. eine Fläche zum Zeichnen der OpenGL-Szene. Sie wird wie jede andere Swing- oder AWT-Komponente eingesetzt. - Klasse GLJPanel: Diese Klasse ist Subklasse von JPanel und bietet dieselbe Funktionalität wie ein GLCanvas. Jedoch erreicht ein GLJPanel nicht dieselbe Performance wie ein GLCanvas. - Klasse GLU: Stellt die bekannten Funktionen der GLU-Bibliothek bereit. Während man von der Klasse GL wie bereits erwähnt automatisch eine Instanz erhält, muss GLU speziell instanziiert werden um genutzt zu werden. Näheres im Tutorial. - Klasse GLUT: Stellt analog zu GLU die entsprechenden Funktionen der GLUT-Bibliothek bereit. Zwar gibt es noch weitere interessante Klassen wie Texture oder Screenshot, diese sollen hier aber aus Zeitgründen nicht näher betrachtet werden. Sie würden das Tutorial außerdem unnötig aufblähen. Alle genannten Klassen sind wie von Java gewohnt im dazugehörigen Javadoc ausführlich beschrieben. Die Dateien sind ebenfalls auf der JOGL-Projekt-Seite zum Download erhältlich. Erwähnenswert ist an dieser Stelle auch eine Erweiterung für die Sun Entwicklungsumgebung NetBeans, nämlich das NetBeans-OpenGL-Pack (https://netbeansopengl-pack.dev.java.net/), das JOGL direkt in die Entwicklungsumgebung integriert und unter anderem einen Shader-Editor bietet. Seite 8 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 3. Installation von JOGL Um JOGL nutzen zu können muss selbstverständlich, falls noch nicht geschehen, zunächst eine Java-Distribution installiert sein. JOGL erfordert mindestens das Java 2 SDK Version 1.4, besser natürlich die aktuellste Version. Das Java SDK ist erhältlich unter http://java.sun.com. Jetzt lädt man sich unter http://jogl.dev.java.net die aktuellste Version von JOGL für sein Betriebssystem herunter. Im Moment ist das die Version 1.1.2, zu finden unter dem Link „Current Nightly Builds“. Ich werde an dieser Stelle nur auf die Installation unter Windows XP eingehen. Hat man das Archiv heruntergeladen, entpackt man es in einen beliebigen Ordner auf der Festplatte. Danach muss das Unterverzeichnis „/lib“ in die Umgebungsvariable PATH des Betriebssystems aufgenommen werden.. WICHTIG: Wenn man nicht nur in einer Entwicklungsumgebung wie Eclipse arbeitet sondern auch auf der Kommandozeile JOGL-Programme kompilieren will, müssen die vollständigen Pfade zu jogl.jar und gluegen-rt.jar zusätzlich in die Umgebungsvariable CLASSPATH aufgenommen werden. Im Anschluss muss der Rechner neu gestartet werden. Zusammengefasst: 1. Aktuelle Java-SDK installieren, mindestens Version 1.4 2. Aktuelle JOGL-Version herunterladen und auf die Festplatte entpacken 3. Pfad zum Unterverzeichnis „/lib“ zur Umgebungsvariable PATH hinzufügen 4. Optional die vollständigen Pfade zu jogl.jar und gluegen-rt.jar zur Umgebungs-variable CLASSPATH hinzufügen, um auf der Kommandozeile kompilieren zu können. 5. Rechner neu starten Seite 9 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 3.1 Screenshots zur Einrichtung der Umgebungsvariablen: Arbeitsplatz Eigenschaften Erweitert Umgebungvariablen Systemvariable auswählen und auf „Bearbeiten“ klicken Sollte die Umgebungsvarible CLASSPATH nicht existieren kann sie mit einem Klick auf „Neu“ einfach erstellt werden. Nicht vergessen: Rechner neu starten, sonst werden die Änderungen an den Umgebungsvariablen nicht wirksam!! Seite 10 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 4. Tutorial in Eclipse Wir wollen nun Schritt für Schritt ein JOGL-Programm in der Entwicklungsumgebung Eclipse erstellen und es mit allen grundlegenden Funktionen von OpenGL austatten. Das fertige Programm soll die Achsen des Koordinatensystems darstellen sowie einen Würfel im Zentrum, der auf Kommando ein- und ausgeblendet sowie in einer Animation gedreht werden kann. Ich benutze dabei die Eclipse-Version 3.4.1 (Englisch) und die JOGL-Version 1.1.2 unter Windows XP. 4.1 Neues Projekt anlegen Wir erzeugen zunächst ein neues Java-Projekt auf herkömmliche Art und Weise. Ich nenne das Projekt „JOGL Tutorial“. Ist dies geschehen klickt man mit der rechten Maustaste auf das Projekt und wählt unter „Build Path“ den Punkt „Configure Buildpath“ aus. Nun fügt man unter dem Punkt „Libraries“ mit der Schaltfläche „Add external JARs“ die beiden Dateien „jogl.jar“ und „gluegen-rt.jar“ aus dem JOGLVerzeichnis dem Projekt hinzu und bestätigt mit „OK“. Seite 11 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 4.2 Ein Swing-Fenster erzeugen Wir wollen nun ein normales Swing-Fenster für unsere Anwendung erzeugen, in dem später unsere Szene dargestellt werden kann. Dazu erstellen wir in unserem Projekt eine neue Klasse „MyWindow“, die die Klasse „javax.swing.JFrame“ erweitert. Die Klasse soll außerdem eine „main“-Funktion bekommen, um das Programm später zu starten. Anschließend können wir beginnen unser Fenster mit Leben zu füllen. Wir erstellen einen parameterlosen Konstruktor in dem wir die Größe, den Titel sowie die ExitFunktion zum Beenden festlegen. Außerdem rufen wir bereits im Konstruktor die Funktion setVisible auf, um das Fenster sofort anzuzeigen. Dadurch reicht es zum starten aus, in der Main-Funktion mit „new MyWindow()“ eine anonyme Instanz zu erzeugen. Seite 12 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 Das Programm ließe sich jetzt bereits starten, allerdings fehlt noch etwas Grundlegendes, nämlich die Zeichenfläche. Wir verwenden dazu ein Objekt der Klasse GLCanvas, welches wir gleich global definieren, um später in anderen Funktionen darauf zugreifen zu können. Alternativ könnte man auch die Klasse GLJPanel benutzen. Diese sollte aus Kompatibilitätsgründen sogar benutzt werden, falls das Fenster noch weitere Swing-Komponenten enthält. Da dies hier allerdings nicht der Fall ist wählen wir die leistungsfähigere GLCanvas. Der Code müsste nun ungefähr so aussehen: package jogl; import javax.media.opengl.GLCanvas; import javax.swing.JFrame; public class MyWindow extends JFrame { private GLCanvas canvas; private MyWindow() { super(); setTitle("JOGL Tutorial"); setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(640, 480); canvas = new GLCanvas(); add(canvas); setVisible(true); } public static void main(String[] args) { new MyWindow(); } } Startet man nun das Programm erscheint ein neues Fenster mit der Größe 640 x 480 das nur eine leere schwarze Fläche, nämlich den GLCanvas, enthält. Um zeichnen zu können fügen wir deshalb im nächsten Schritt einen GLEventListener hinzu. Seite 13 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 4.3 Einen GLEventListener implementieren Um die GLCanvas nutzen zu können benötigen wir einen GLEventListener. Wie bereits erwähnt enthält dieser die für OpenGL charakteristischen Callback-Methoden „init“, „reshape“ und „display“. Außerdem die Methode „displayChanged“, die hier allerdings nicht benutzt wird. Dadurch wird es erst möglich, etwas zu zeichnen. GLEventListener ist ein Interface, es würde also genügen unsere Klasse „MyWindow“ dieses Interface implementieren zu lassen und dessen Funktionen zu überschreiben. Der Übersicht zuliebe erstellen wir aber eine Innere Klasse MyGLEventListener am Ende der vorhandenen Klasse. Der Code dazu sieht folgendermaßen aus: private class MyGLEventListener implements GLEventListener { public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) { } public void init(GLAutoDrawable drawable) { } public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { } public void display(GLAutoDrawable drawable) { } } Dieser GLEventListener wird nun an das GLCanvas-Objekt angehängt und steuert von da an sein Verhalten. Dies geschieht mit folgender Zeile, die wir einfach in den Konstruktor des Fensters einfügen: canvas.addGLEventListener(new MyGLEventListener()); Im Gegensatz zu einem C-Programm muss man dem Programm nun nicht mehr explizit die charakteristischen Funktionen vorgeben, die geschieht automatisch durch den GLEventListener. Wir können nun mit dem Zeichnen beginnen. Seite 14 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 4.4 Ein Koordinatensystem zeichnen Wie bereits angekündigt wollen wir zunächst die Achsen des Koordinatensystems in die Szene einzeichnen. Dazu müssen zunächst die Funktion „init“ und „reshape“ ausfüllen, denn „init“ wird beim erstellen des Fensters aufgerufen und „reshape“ bei jeder Größen- oder Positionsänderung. Wir verwenden die folgenden, bereits aus den Vorlesungen bekannten Befehle: public void init(GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glClearColor(0, 0, 0, 0); gl.glEnable(GL.GL_DEPTH_TEST); gl.glClearDepth(1.0); gl.glDepthFunc(GL.GL_LESS); } public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { GL gl = drawable.getGL(); GLU glu = new GLU(); gl.glViewport(x, y, width, height); gl.glMatrixMode(GL.GL_PROJECTION); gl.glLoadIdentity(); if (width <= height) gl.glOrtho(-2, 2, -2 * ((double) height) / (double) width, 2 * ((double) height) / (double) width, -5, 5); else gl.glOrtho(-2 * (double) width / ((double) height), 2 * (double) width / ((double) height), -2, 2, -5, 5); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glLoadIdentity(); glu.gluLookAt(0.5, 0.5, 0.5, 0, 0, 0, 0, 1, 0); } Zu Beginn jeder Funktion holt man sich vom GLEventListener ein GL-Objekt für den Rendering-Kontext. Sollten außerdem Objekte aus GLU oder GLUT benötigt werden, können die beiden Klasse einfach instanziiert werden, wie in der Funktion „reshape“ zu sehen. Wir löschen in der Funktion „init“ zunächst den Farb-Puffer und schalten den TiefenTest ein, da wir in 3D zeichnen wollen. In der Funktion „reshape“ legen wir den Viewport und das Viewing-Volumen fest , stellen die Art der Projektion ein und bestimmen den Betrachtungswinkel der Kamera mit „gluLookAt“. Diese Funktionen dürften nicht neu sein. Seite 15 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 Nun folgt die Funktion „display“, in der das Koordinatensystem gezeichnet wird: public void display(GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); GLUT glut = new GLUT(); gl.glColor3f(1.0f, 0.5f, 0.0f); // ORANGE for(int i = 0; i < 6; i++) { gl.glPushMatrix(); if (i < 4) gl.glRotatef(i * 90.0f, 0.0f, 1.0f, 0.0f); if (i == 4) gl.glRotatef(90.0f, 1.0f, 0.0f, 0.0f); if (i == 5) gl.glRotatef(90.0f,-1.0f, 0.0f, 0.0f); gl.glTranslatef(0.0f, 0.0f, 1.5f); gl.glBegin(GL.GL_LINES); { gl.glVertex3f( 0.0f, 0.0f, 0.0f); gl.glVertex3f( 0.0f, 0.0f,-1.5f);} gl.glEnd(); glut.glutWireCone(0.05f, 0.25f, 10, 5); gl.glRasterPos3f(-0.1f, 0.1f, 0.4f); switch (i) { case 0: drawText("Z"); break; case 1: drawText("X"); break; case 2: drawText("-Z"); break; case 3: drawText("-X"); break; case 4: drawText("-Y"); break; case 5: drawText("Y"); break; } gl.glPopMatrix(); } } private void drawText(String text) { GLUT glut = new GLUT(); for(int i = 0; i < text.length(); i++) glut.glutBitmapCharacter(GLUT.BITMAP_9_BY_15, text.charAt(i)); } Auch hier wird zunächst ein Objekt der Klasse GL geholt. Danach werden wie schon bekannt die Puffer initialisiert. Außerdem erzeugen wir uns ein GLUT-Objekt und stellen die Zeichenfarbe auf orange ein. Jetzt wird sechs mal eine Linie (Verfahren ist bekannt) mit einer Pfeilspitze (glutWireCone aus der GLUT) gezeichnet, mit einer Beschriftung versehen, aus dem Ursprung herausgeschoben und dann in die entsprechende Richtung gedreht. GLPushMatrix und GLPopMatrix werden eingesetzt, damit sich die Translationen und Drehungen nur jeweils auf den aktuellen Pfeil auswirken. Die Hilfsfunktion „drawText“ zeichnet Buchstaben in die Szene ein mit Hilfe einer GLUT-Funktion. Die Funktion „glRasterPos3f“ bestimmt dabei die Position des Textes in Abhängigkeit von der letzten Stelle an der gezeichnet wurde. Seite 16 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 Es müsste sich nach dem Starten des Programms folgendes Fenster öffnen: Und schon haben wir unsere erste OpenGL-Szene erstellt. Das Fenster kann nun dank der Funktion „reshape“ beliebig verschoben und in der Größe geändert werden, ohne das das Bild zu verzerren. Im nächsten Abschnitt wollen wir das Programm jetzt dahingehend erweitern, das im Ursprung ein Würfel angezeigt wird, der mit einem Tastendruck aus- und wieder eingeblendet werden kann. Seite 17 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 4.5 Tastatur-Ereignisse verarbeiten Unser Programm soll nun auf Tastatureingaben reagieren und einen Würfel im Ursprung aus- und wieder einblenden können. Dazu benötigen wir wie aus Java bekannt einen KeyListener. Wir erstellen dazu wiederum eine innere Klasse mit Namen „MyKeyListener“, die das Interface KeyListener implementiert. private class MyKeyListener implements KeyListener { public void keyPressed(KeyEvent e) { } public void keyReleased(KeyEvent e) { } public void keyTyped(KeyEvent e) { switch (e.getKeyChar()) { case 0x1B: System.exit(0); break; case 0x20: showCube = !showCube; canvas.display(); break; default: break; } } } Es soll folgende Eingabemöglichkeiten geben: - Escape-Taste (Hexcode 1B): Programm beenden - Leertaste (Hexcode 20): Würfel ein- und ausblenden Das Ein- und Ausblenden des Würfels wird über eine globale, Boole’sche, mit „false“ initialisierte Variable „showCube“ realisiert. Dazu wird die Funktion „display“ des GLEventListeners wie folgt ergänzt und bei jedem Druck auf die Leertaste erneut aufgerufen (Die Funktion „canvas.display()“ ruft die „display“-Funktion des angehängten GLEventListeners auf): if (showCube) { gl.glColor3f(1.0f, 1.0f, 1.0f); glut.glutWireCube(0.8f); } Seite 18 // WEISS JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 Der KeyListener muss jetzt nur noch mit dem Fenster verknüpft werden. Dazu wird er aber nicht etwa wie gewohnt an den JFrame oder die GLCanvas angehängt, sondern an der Rendering-Kontext von OpenGL, also das Objekt der Klasse GLAutoDrawable in der Funktion „init“ des GLEventListeners. Die Funktion wird um diese Zeile ergänzt: drawable.addKeyListener(new MyKeyListener()); Jetzt kann das Programm gestartet werden. Nach einem Klick auf das Zeichenfeld kann mit der Leertaste ein Würfel eingeblendet und mit Escape das Programm beendet werden. Das entstandene Bild sieht durch den unglücklich gewählten Blickwinkel allerdings nicht sehr dreidimensional aus. Dies wollen wir im nächsten Kapitel ändern, in dem wir mit der Maus die Szene drehen. Seite 19 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 4.6 Maus-Ereignisse verarbeiten Es soll nun ermöglicht werden, mit der Maus die Szene zu drehen. Dazu benötigen wir diesmal zwei Listener, nämlich einen MouseListener zur Überwachung der Tasten und einen MouseMotionListener um auf Ziehen zu testen. Sie werden genau wie der KeyListener an das GLAutoDrawable-Objekt angehängt. Ihr Code sieht so aus: private class MyMouseMotionListener implements MouseMotionListener { public void mouseDragged(MouseEvent e) { int x = e.getX(); int y = e.getY(); viewRotY = ((x - dragStartX) / (float) getWidth()) * 360.0f; viewRotX = ((y - dragStartY) / (float) getHeight()) * 360.0f; dragStartX = e.getX(); dragStartY = e.getY(); canvas.display(); } public void mouseMoved(MouseEvent arg0) { } } private class MyMouseListener implements MouseListener { public void mouseClicked(MouseEvent arg0) { } public void mouseEntered(MouseEvent arg0) { } public void mouseExited(MouseEvent arg0) { } public void mousePressed(MouseEvent e) { dragStartX = e.getX(); dragStartY = e.getY(); } public void mouseReleased(MouseEvent arg0) { } } Die Variablen viewRotX/Y (Float) und dragStartX/Y (Integer) müssen global definiert und mit 0 initialisiert werden. In der „display“-Funktion fügt man jetzt VOR dem Zeichnen der Objekte folgendes ein: gl.glRotatef(viewRotX, 1.0f, 0.0f, 0.0f); gl.glRotatef(viewRotY, 0.0f, 1.0f, 0.0f); Damit wird die gesamte Szene gedreht und währenddessen ständig neu gezeichnet. Seite 20 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 4.7 Animation hinzufügen Als Abschluss des Tutorials soll sich der Würfel in einer Animation um die Y-Achse drehen. Dazu gibt es zwei Möglichkeiten. Zum einen könnte man die JOGL-Klassen Animator oder FPSAnimator benutzen, die die „display“-Funktion ständig neu aufrufen. Diese sind allerdings nur in einfachen Fällen geeignet, da man in dem Thread nichts anderes ausführen kann, wie zum Beispiel Frames zählen. Deshalb schreiben wir unseren eigenen Animations-Thread, der wie folgt aussieht: private class AnimationThread extends Thread { private int fps; public AnimationThread(int fps) { super(); this.fps = fps; } public void run() { while (animation) { viewRotX = 0; viewRotY = 0; cubeRot += 1.0f; try { sleep(1000 / fps); } catch (Exception e) {;} canvas.display(); } } } Er bildet im Prinzip genau den FPSAnimator von JOGL nach, nur dass man die drei Wertzuweisungen in der „display“-Funktion machen müsste. Bei manchen Operationen ist dies allerdings nicht möglich. In unserem Fall würde die Variable „cubeRot“, die die Drehung des Würfels bestimmt, auch beim Ändern des Betrachtungswinkels hochgezählt. Oder will man zum Beispiel die Frames mitzählen um den FPSAnimator zu testen kann man dies nicht in der „display“-Funktion tun, denn diese wird auch beim Ziehen der Maus aufgerufen. Es empfiehlt sich daher eigene Threads zu benutzen, da sie einfacher zu erweitern sind. Seite 21 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 Für die Animation muss aber noch mehr Code geschrieben werden. Es muss eine globale Boole’sche Variable „animation“ geben, um den Thread stoppen zu können. Außerdem muss der KeyListener ergänzt werden: case 0x0A: if (!animation) { animation = true; new AnimationThread(50).start(); } else { animation = false; } break; Hier wird beim Drücken der Eingabetaste die Variable „animation“ geändert und ggf. ein AnimationThread mit einer bestimmten Framerate gestartet. Des Weiteren muss die „display“-Funktion angepasst werden: public void display(GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); GLUT glut = new GLUT(); gl.glRotatef(viewRotX, 1.0f, 0.0f, 0.0f); gl.glRotatef(viewRotY, 0.0f, 1.0f, 0.0f); gl.glColor3f(1.0f, 0.5f, 0.0f); // ORANGE for(int i = 0; i < 6; i++) { gl.glPushMatrix(); if (i < 4) gl.glRotatef(i * 90.0f, 0.0f, 1.0f, 0.0f); if (i == 4) gl.glRotatef(90.0f, 1.0f, 0.0f, 0.0f); if (i == 5) gl.glRotatef(90.0f,-1.0f, 0.0f, 0.0f); gl.glTranslatef(0.0f, 0.0f, 1.5f); gl.glBegin(GL.GL_LINES); { gl.glVertex3f( 0.0f, 0.0f, 0.0f); gl.glVertex3f( 0.0f, 0.0f,-1.5f);} gl.glEnd(); glut.glutWireCone(0.05f, 0.25f, 10, 5); gl.glRasterPos3f(-0.1f, 0.1f, 0.4f); switch (i) { case 0: drawText("Z"); break; case 1: drawText("X"); break; case 2: drawText("-Z"); break; case 3: drawText("-X"); break; case 4: drawText("-Y"); break; case 5: drawText("Y"); break; } gl.glPopMatrix(); } if (showCube) { gl.glPushMatrix(); gl.glColor3f(1.0f, 1.0f, 1.0f); // WEISS gl.glRotatef(cubeRot, 0.0f, 1.0f, 0.0f); glut.glutWireCube(0.8f); gl.glPopMatrix(); } Seite 22 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 Wie man sieht wird der Würfel jetzt noch um den Wert „cubeRot“ gedreht, welcher global sein muss und in dem AnimationThread hochgezählt wird. Ausserdem wird das Zeichnen des Würfels jetzt auch von glPushMatrix und glPopMatrix eingeschlossen, das sich die glRotate-Funktionen sonst gegenseitig beeinflussen würden. Damit sind wir bereits am Ende des Tutorials angelangt und haben somit alle grundlegenden Funktionen für die JOGL-Programmierung kennengelernt. Seite 23 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 5. Eigenes Beispiel: ASURO-Roboter Um eine praktische Anwendung zu zeigen, habe noch ein etwas komplexeres Beispiel kreiert. Es handelt sich um eine Simulation eines ASURO-Roboters, der einer Linie hinterherfährt. Der ASURO ist ein etwa 10cm langer, frei programmierbarer Experimentier-Roboter, der über zwei Motoren und diverse Sensoren und LEDs verfügt. Darunter auch zwei Helligkeitssensoren an der Unterseite, die es ihm ermöglichen, bei entsprechender Programmierung einer Linie zu folgen. Mein Beispielprogramm erzeugt nach Benutzervorgaben eine Linie auf einem Untergrund, der der ASURO animiert folgt, inklusive leuchtender LEDs, beweglichem Schalter und drehenden Zahn- und Antriebsrädern. Die Linie kann zufällig verlaufen, Zickzackförmig, Sinusförmig oder gerade. Der Benutzer kann zwischen den einzelnen Modi per Taste wählen. Das Programm beinhaltet außerdem alle Funktionen des Tutorials und hat insgesamt circa 850 Zeilen inklusive Kommentaren. Es liegt dieser Ausarbeitung bei und kann einfach in Eclipse importiert werden, vorausgesetzt JOGL ist installiert. Folgende Aktionen sind möglich: - Tasten „1“ – „4“: Art der Linie ändern (Gerade, Zufällig, Zickzack oder Sinus) - Taste „a“: Koordinatensystem aus- und einblenden - Taste „f“: Fußboden aus- und einblenden - Taste „r“: Roboter aus- und einblenden - Leertaste: Ansicht zurücksetzen - Eingabetaste: Animation starten und stoppen - Escape: Programm beenden Mit der Maus kann der Blickwinkel geändert werden. Statt zu Simulieren könnte man das Programm auch mit echten Daten füttern, leider hat dafür aber die Zeit nicht gereicht. Seite 24 JOGL Java Binding for the OpenGL API Michael Kremer WS 2008/2009 6. Fazit Mir hat die Arbeit mit JOGL viel Spaß gemacht. Wenn man bereits in C OpenGLAnwendungen geschrieben hat, ist es ein Leichtes sich auf JOGL umzustellen und man findet schnell Gefallen an den Vorteilen von Java. Bei der Programmierung sind keine nennenswerten Schwierigkeiten aufgetreten und alles hat wie beschrieben funktioniert. Gerne wäre ich noch auf Texturen und Materialien eingegangen, aber leider haben Zeit und Umfang der Ausarbeitung das verhindert. Ich kann aber jedem nur empfehlen sich mit JOGL zu beschäftigen. Es gibt im Internet genug Beispiele und Tutorials, damit auch Anfänger schnell einen Einstieg finden. 7. Quellen https://jogl.dev.java.net/ Offizielle Projekt-Seite http://de.wikipedia.org/wiki/Jogl JOGL bei Wikipedia http://www.jogl.info Info-Seite mit Tutorial http://www.google.de/ Die beste Bildersuchmaschine JOGL Javadoc Beschreibung der JOGL API JOGL Userguide Installationshinweise und Einführung Seite 25