Spiele-Entwicklung in Java - Online
Transcription
Spiele-Entwicklung in Java - Online
Spiele-Entwicklung in Java Wolfgang Hochleitner DIPLOMARBEIT eingereicht am Fachhochschul-Masterstudiengang Digitale Medien in Hagenberg im Juni 2006 Copyright 2006 Wolfgang Hochleitner Alle Rechte vorbehalten ii Erklärung Hiermit erkläre ich an Eides statt, dass ich die vorliegende Arbeit selbstständig und ohne fremde Hilfe verfasst, andere als die angegebenen Quellen und Hilfsmittel nicht benutzt und die aus anderen Quellen entnommenen Stellen als solche gekennzeichnet habe. Hagenberg, am 22. Juni 2006 Wolfgang Hochleitner iii Inhaltsverzeichnis Erklärung iii Vorwort vii Kurzfassung viii Abstract ix 1 Einleitung 1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Problemstellung . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3 Aufbau dieser Arbeit . . . . . . . . . . . . . . . . . . . . . . . 2 Spiele-Entwicklung allgemein 2.1 Was ist Spiele-Entwicklung? . . . . . . . 2.1.1 Game-Design . . . . . . . . . . . 2.1.2 Game-Programming . . . . . . . 2.2 Grafik-Schnittstellen und Engines . . . . 2.2.1 OpenGL . . . . . . . . . . . . . . 2.2.2 Direct3D . . . . . . . . . . . . . 2.2.3 Middleware . . . . . . . . . . . . 2.3 Wichtige Konzepte . . . . . . . . . . . . 2.3.1 Szenengraphen . . . . . . . . . . 2.3.2 Scene-Management . . . . . . . . 2.3.3 BSP-Bäume . . . . . . . . . . . . 2.4 Existierende Arbeiten auf diesem Gebiet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Ansätze in Java 3.1 Grafik-Schnittstellen und Engines . . . . . . . . . . . . . . . 3.1.1 Java 3D . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.2 OpenGL-Anbindungen für Java . . . . . . . . . . . . 3.1.3 Middleware-Anbindungen und Szenengraphen-APIs 3.2 Existierende 3D-Java-Spiele . . . . . . . . . . . . . . . . . . iv 1 1 2 3 . . . . . . . . . . . . 5 5 5 7 9 9 10 12 13 13 16 18 20 . . . . . 22 22 22 27 29 30 INHALTSVERZEICHNIS v 4 JSceneViewer3D 4.1 Architektur . . . . . . . . . . . . . . 4.2 Basis-Applikation . . . . . . . . . . . 4.3 Level-Loader . . . . . . . . . . . . . 4.3.1 Auslesen der Lump-Daten . . 4.4 Rendering . . . . . . . . . . . . . . . 4.4.1 Erstellung des Szenengraphen 4.4.2 Darstellung der Szene . . . . 4.5 Logger . . . . . . . . . . . . . . . . . 4.5.1 log4j . . . . . . . . . . . . . . 4.5.2 Implementierung des Loggers 4.5.3 Aufrufe des Loggers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 33 35 39 42 47 48 52 55 55 56 57 5 Auswertung der Performance-Daten 5.1 Test-Voraussetzungen und Ablauf . . 5.1.1 Die getesteten Levels . . . . . 5.2 Tests mit JSceneViewer3D . . . . . . 5.3 Tests mit Quake III Arena . . . . . . 5.4 Vergleich und Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 58 60 60 65 68 6 Evaluierung und Schlussbemerkungen 6.1 Evaluierung . . . . . . . . . . . . . . . 6.1.1 Verfügbarkeit von Software . . 6.1.2 Produktivität . . . . . . . . . . 6.1.3 Performance . . . . . . . . . . 6.2 Fazit . . . . . . . . . . . . . . . . . . . 6.3 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 71 71 73 74 74 75 . . . . . . . . . . . . . . . 76 76 77 77 78 78 78 78 79 79 80 80 80 80 81 81 A Quake III Level Format A.1 Dateistruktur . . . . . . . . . . A.2 Datentypen . . . . . . . . . . . A.3 Header und Verzeichniseinträge A.4 Lumps . . . . . . . . . . . . . . A.4.1 Entities (Entitäten) . . A.4.2 Textures (Texturen) . . A.4.3 Planes (Ebenen) . . . . A.4.4 Nodes (Knoten) . . . . A.4.5 Leafs (Blätter) . . . . . A.4.6 Leaffaces . . . . . . . . A.4.7 Leafbrushes . . . . . . . A.4.8 Models (Modelle) . . . . A.4.9 Brushes . . . . . . . . . A.4.10 Brushsides . . . . . . . A.4.11 Vertexes (Vertices) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . INHALTSVERZEICHNIS A.4.12 A.4.13 A.4.14 A.4.15 A.4.16 A.4.17 vi Meshverts . . . . . . . . . . . . . . . Effects (Effekte) . . . . . . . . . . . Faces . . . . . . . . . . . . . . . . . Lightmaps . . . . . . . . . . . . . . . Lightvols (Beleuchtungsdaten) . . . Visdata (Sichtbarkeitsinformationen) B Inhalt der DVD B.1 Diplomarbeit . . . B.2 Literatur . . . . . . B.3 Performance-Daten B.3.1 Log-Dateien B.3.2 Videos . . . B.4 Quellcode . . . . . Literaturverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 82 82 83 83 84 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 85 85 86 86 87 87 88 Vorwort Diese Diplomarbeit entstand im vierten Semester des Studiengangs Digitale Medien (Schwerpunkt Computer Games) an der Fachhochschule Oberösterreich, Campus Hagenberg. Sie stellt den Abschluss des Magisterstudiums dar und steht am Ende einer insgesamt 5-jährigen Ausbildung, die ich an dieser Hochschule absolvieren konnte. Ich möchte mich an dieser Stelle bei jenen Personen bedanken, die mich beim Schreiben dieser Arbeit und auch während meines gesamten Studiums unterstützt haben. Mein Dank geht hierbei vor allem an meine Eltern Fritz und Maria, die mir zu jeder Zeit den nötigen Rückhalt gegeben haben. Weiters danke ich meiner Freundin Christina Köffel, die in all der Zeit immer zu mir gestanden ist und meinen Blick immer nach vorne gerichtet hat. Mein abschließender Dank ergeht an meinen Betreuer Roman Divotkey, der mir bei der Umsetzung des Projekts und der Aufarbeitung des in der Arbeit beschriebenen Themas stets hilfreich zur Seite gestanden ist. vii Kurzfassung Die vorliegende Arbeit befasst sich mit dem Thema Spiele-Entwicklung unter Verwendung der Programmiersprache Java. Zu Beginn wird eine allgemeine Definition des Begriffs Spiele-Entwicklung“ gegeben. Dabei wird vor ” allem auf die wichtigen Teilbereiche Game-Design und Game-Programming sowie die entstehenden Rollenbilder eingegangen. In weiterer Folge wird der technische Stand der Dinge bei der Umsetzung von Spielen erklärt. Das Hauptaugenmerk liegt auf den Grafik-Schnittstellen OpenGL und Direct3D sowie diverser Middleware. Im Anschluss werden bestehende Arbeiten zum gewählten Thema aufgelistet. Es wird ein Überblick über existierende Grafik-Anbindungen und Spiele in Java gegeben, bevor die Umsetzung einer Applikation zum Laden von 3D-Spiele-Levels (JSceneViewer3D) beschrieben wird. JSceneViewer3D lädt beliebige Welten des Spiels Quake III Arena und stellt diese mittels Java 3D dar. Performance-Daten wie die Framerate oder die Polygonanzahl werden vom Programm gespeichert. Die Arbeit präsentiert die Ergebnisse diverser Testläufe der Applikation und vergleicht sie mit Werten des Original-Spiels. viii Abstract The present work describes the process of game development using the programming language Java. A general definition of the term “game development” is given by mainly focussing on the areas game design and game programming in addition to describing the resulting roles. In what follows the technical state of the art of realizing games is described. The particular emphasis is placed on the graphical interfaces OpenGL and Direct3D as well as on middleware. In the subsequent section existing work on the chosen topic is presented. A review of existing Java graphic-bindings and Java games is given prior to the implementation process of JSceneViewer3D–an application for loading 3D game levels. JSceneViewer3D loads maps of the computer game Quake III Arena and displays them by using Java 3D technology. The program also stores performance data such as the frame rate or number of polygons. The results of various test runs are presented and compared with data from the original game. ix Kapitel 1 Einleitung 1.1 Motivation Für den Endanwender und somit Käufer zählen bei einem Computerspiel grob unterteilt zwei Dinge: Der gute Inhalt und die gute Umsetzung. Blickt man nun hinter die Kulissen und wirft einen Blick auf eben jene Umsetzung, so ist in den Bereichen Konsole, PC und Automaten eindeutig eine Dominanz von Spielen, die in den Programmiersprachen C oder C++ entwickelt wurden, zu erkennen. In diversen Artikeln rund um das Thema Spiele-Entwicklung finden sich immer wieder Werte um 99,5 Prozent zugunsten der zuvor genannten Sprachen. Genaue statistische Daten gibt es keine, dennoch dürfte der Wert durchaus der Realität entsprechen. Lediglich in den Bereichen Mobile- und Online-Gaming sind Spiele, die auf der JavaTechnologie basieren, sehr weit verbreitet. Diese Prozentzahlen betreffen übrigens einen Markt, der allein in den USA im Jahr 2005 einen Gesamtumsatz von 248 Millionen Dollar bei Computer- und Videospielen erzielt hat [3]. Nun drängt sich berechtigterweise die Frage auf, warum sich Java in den klassischen Bereichen der Spiele-Entwicklung nicht durchzusetzen vermag. Eine unter Buch-Autoren und Java-Experten verbreitete Meinung ist der schlechte Ruf von Java im Bezug auf Geschwindigkeit und Performance (Performanz, Leistungsmerkmale – siehe dazu z. B. [2] oder [21]). Weitere gern genannte Argumente sind eine oftmals falsche Auffassung von Java als Sprache ausschließlich für mobile Anwendungen und das Web oder die Tatsache, dass Java nicht für Konsolen verfügbar ist. Oft ist auch zu lesen, dass Java im Bereich der Spiele-Entwicklung ohnehin bereits tot“ sei. ” Das Ziel dieser Diplomarbeit ist es, anhand eines praktisch umgesetzten Beispiels herauszufinden, wie sehr sich Java – ungeachtet der soeben genannten Vorurteile – für den Einsatz im Spielebereich eignet. 1 KAPITEL 1. EINLEITUNG 1.2 2 Problemstellung Um den kompletten Prozess der Spiele-Entwicklung zu durchlaufen, wäre die Erstellung eines völlig neuen Spiels in Java sicherlich die beste Option. Da es sich bei dieser Diplomarbeit und dem dazugehörigen Projekt jedoch um eine Einzelarbeit handelt, wäre die daraus resultierende Menge an Arbeit nicht in einem bzw. zwei Semestern zu bewältigen gewesen. Um jedoch trotzdem aussagekräftige Ergebnisse zu bekommen, wurde ein Teil eines Spiels herausgegriffen und realisiert. Dabei fiel die Auswahl auf das Laden und Darstellen eines 3D-SpieleLevels. Nach einer Evaluierung der gängigsten Spiele wurde Quake III Arena ausgewählt. Gründe dafür waren vor allem die gute Verfügbarkeit von Informationen über das Level-Format (siehe Anhang A) und das Vorhandensein von existierenden Lösungen in diesem Bereich, an denen eine Orientierung möglich war. Keine dieser Lösungen realisierte allerdings den hier im Endeffekt gewählten Ansatz. Die Geometrie dieser Levels ist bereits beträchtlich komplexer als die simpler geometrischer Primitive. Neben der Darstellung von Polygonen (ebenen Flächen) ist auch die Berechnung von gekrümmten Flächen im Raum (Bézier Patches) erforderlich. Zusätzlich werden diese Flächen texturiert und Lightmaps kommen zum Einsatz. Zieht man dies in Betracht, so spielt die Performance durchaus eine Rolle, denn schließlich ist eine hohe Framerate (Bilder pro Sekunde) eines der Hauptkriterien für eine zufriedenstellende Echtzeit-Darstellung von 3D-Szenen. Als daraus resultierendes Ziel ergibt sich nun, beliebige Levels dieses Spieles in Java zu laden und mit einem Java API (Java 3D oder eine Java OpenGL Anbindung) performant darzustellen. Als API wurde Java 3D gewählt, da es sehr stark die Java-Philosophie propagiert: Einfachheit, Sicherheit, allerdings in manchen Fällen auch nicht volle bzw. direkte Kontrolle. Eine genauere Erläuterung zur Funktionsweise von Java 3D findet sich in Abschnitt 3.1.1. Java OpenGL Anbindungen werden im Abschnitt 3.1.2 näher erläutert. Eine weitere Grundanforderung an die Applikation ist eine einfache Erweiterungsmöglichkeit. Es soll darauf geachtet werden, dass es ohne viel Aufwand möglich ist, auch andere Level-Formate zu unterstützen, d. h. zu laden und darzustellen. Für die vorliegende Version wurde jedoch aufgrund der am Anfang dieses Abschnitts angeführten zeitlichen Begrenzung nur Quake III mit Java 3D Darstellung realisiert. So stand am Beginn des Projektes die Einarbeitung in das Java 3D API sowie in das Quake III Level-Format. Da für Java 3D kein Quake III LevelLoader vorhanden war, musste dieser von Grund auf konzipiert und realisiert werden. Zwar gab es bereits bestehende Loader, diese waren jedoch alle für C++ oder Java OpenGL Anbindungen vorhanden. Daher konnten sie nur bedingt als Ausgangsbasis bzw. Vorlage dienen. KAPITEL 1. EINLEITUNG 3 Schließlich soll die fertige Applikation auch Auskunft darüber geben können, wie performant die von ihr erzeugte Darstellung auch tatsächlich ist. Dazu misst sie verschiedene Daten und fasst diese in einer Log-Datei zusammen, welche im Anschluss ausgewertet werden kann. Die genaue Umsetzung des Projektes wird im Laufe dieser Arbeit behandelt und dient als Veranschaulichung, wie die Entwicklung eines Spieles in Java gehandhabt werden kann. 1.3 Aufbau dieser Arbeit Die vorliegende Arbeit soll den Leser schrittweise an den Spiele-Entwicklungsprozess in Java heranführen. Kapitel 2 dient als Einführungskapitel und beschreibt einige allgemeine Aspekte zum Thema Spiele-Entwicklung. Zunächst erfolgt eine Erklärung des Begriffs an sich, da dieser im Sprachgebrauch zwar durchaus oft, aber auch immer wieder falsch verwendet wird. Anschließend erfolgt eine Erläuterung zu den gängigsten Grafik Schnittstellen, um den so genannten State of the Art“ in der Spiele-Entwicklung aufzuzeigen. Nachdem die tech” nischen Details geklärt wurden, wird noch auf einige wichtige Konzepte bei der Umsetzung von Spielen eingegangen. Diese sind Szenegraphen, SzenenManagement und im Besonderen BSP-Bäume. Diese Konzepte sind vor allem für das Verständnis vom Aufbau von Quake III Levels von besonderer Bedeutung. Ebenfalls in diesem Kapitel enthalten ist eine Übersicht über bereits bestehende Arbeiten auf diesem Gebiet, die sich auch mit SpieleEntwicklung in Java beschäftigen. Kapitel 3 dient als Fortführung von Kapitel 2, es wird jedoch spezifisch auf Java und seine Konzepte eingegangen. Hierbei werden wiederum die gängigsten Schnittstellen zur Darstellung vorgestellt, auch so genannte Java-Middleware wird hierbei angesprochen. Abgeschlossen wird dieses Kapitel mit einer Übersicht über aktuelle Spiele in Java, um zu zeigen, welche praktisch umgesetzten und auch kommerziell verwertbaren Ergebnisse auf dem Gebiet dieser Diplomarbeit bereits erzielt wurden. In Kapitel 4 wird dann genau auf die Realisierung der eigenen Applikation eingegangen. Hierbei wird der gesamte Entwicklungsprozess beschrieben und erläutert, wie von einem vorgegebenen Quake III Level die endgültige Darstellung am Bildschirm erreicht wurde. Dieses Kapitel setzt die vorigen Teile der Diplomarbeit voraus und nimmt immer wieder Bezug auf darin enthaltene Konzepte. Die von der Applikation generierten Performance-Daten werden in Kapitel 5 analysiert und mit Daten des Originalspiels verglichen. Dieses Kapitel soll durch Zahlen und Fakten genaueren Aufschluss darüber geben, ob Java für einen Einsatz im Spielebereich geeignet ist oder nicht. Schließlich soll über die gewonnenen Erkenntnisse in Kapitel 6 resümiert KAPITEL 1. EINLEITUNG 4 werden und eine konkrete Antwort auf die hier in der Einleitung gestellten Fragen gegeben werden. Abgeschlossen wird dieses Kapitel durch Ausblicke auf zukünftige Arbeiten oder Weiterentwicklungen am bestehenden Projekt. Kapitel 2 Spiele-Entwicklung allgemein 2.1 Was ist Spiele-Entwicklung? Befasst man sich mit dem Thema Spiele-Entwicklung – egal ob in kreativer oder technischer Hinsicht – ist es wichtig, immer den Gesamtprozess vor Augen zu haben. Ein Computerspiel entsteht nicht allein am Papier, genauso wenig aber wird es entwickelt, in dem man sich mit einer Idee vor den Computer setzt und zu programmieren beginnt. Vielmehr ist Spiele-Entwicklung ein durchgehender und mitunter auch langwieriger Prozess, der mit einer Idee beginnt, sich in der genauen Formulierung der selben fortsetzt und mit der Implementierung endet. 2.1.1 Game-Design Spiel ist eine freiwillige Handlung oder Beschäftigung, die innerhalb gewisser Grenzen von Zeit und Raum nach freiwillig angenommenen, aber unbedingt bindenden Regeln verrichtet wird, ihr Ziel in sich selber hat und begleitet wird von einem Gefühl der Spannung und Freude und einem Bewusstsein des Andersseins“ ” als das gewöhnliche Leben“. ” Diese Definition nach Huizinga [9] war Ende der 1930er Jahre, als sie entstand, natürlich noch nicht auf Computerspiele ausgerichtet. Dennoch gelten diese Richtlinien hier ebenso, da lediglich das Medium, das als Grundlage für das Spiel dient, ein anderes bzw. neues ist. Bevor jedoch Regeln und Ziele eines Spieles festgelegt werden können, muss eine Idee vorhanden sein. Diese ist im Normalfall noch vage formuliert und existiert zumeist auch nur im Kopf. Bis zum fertigen Produkt ist es von hier an noch ein sehr weiter Weg. Doch je genauer jener Weg bereits im Anfangsstadium geplant wird, desto schneller kann er bewältigt werden. Diese Planung ist das Game-Design. Grob gesagt beinhaltet es alles, das nicht technisch ist, also nichts mit der Umsetzung (d. h. Programmierung) zu 5 KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN 6 tun hat. Genauer gesagt bedeutet dies, die Grundidee des Spiels so detailliert auszuführen und nieder zu schreiben, dass auf der Basis des so entstandenen Dokuments die technische Umsetzung erfolgen kann. Rollings und Adams [18] definieren Game-Design als Prozess, der aus folgenden Teilen aufgebaut ist: Ein Spiel ausdenken. Definieren, wie es funktioniert. Die Elemente, die das Spiel bestimmen (konzeptuelle, funktionale, künstlerische und andere), beschreiben. Diese Informationen dem Team, welches das Spiel umsetzen wird, weiterleiten. Aus dieser Definition ist erkennbar, dass die Entwicklung eines Spieles kein Prozess ist, der von einer Person alleine abgewickelt wird. Vielmehr arbeiten an einem modernen Computerspiel unzählige Personen in einem Team zusammen. Damit alle diese Personen wissen, wie das gemeinsame Ziel aussieht, ist eine lückenlose Definition und Dokumentation der Spielidee unabdingbar. Dafür haben sich im Game-Design typischerweise drei Typen von Design-Dokumenten etabliert, die das Spiel in unterschiedlichen Detailgraden beschreiben. Das High-Concept beschreibt auf etwa zwei bis vier Seiten die Grundzüge des Spiels. Dazu gehören primär die Handlung und das Gameplay (was muss der Spieler tun, wie wird sich das Spiel entwickeln), aber auch eher nüchterne Details wie das Genre, das Zielpublikum oder die Plattform, für die das Spiel entwickelt werden soll. Am wichtigsten im High-Concept ist jedoch das Verdeutlichen von so genannten Unique Selling Propositions“, ” also Dingen, die das Spiel einzigartig machen. Diese sollen ins Auge stechen und klar machen, warum es sich lohnt, dieses Spiel zu entwickeln und zu verkaufen. Somit dient das High-Concept Dokument auch als erster Überblick für potentielle Produzenten oder Publisher. Eine wesentlich genauere Spezifizierung des Spiels stellt in weiterer Folge dann das Game-Treatment dar. Es umfasst in der Regel bereits zehn bis zwanzig Seiten, beschreibt das Spiel jedoch immer noch nicht bis in alle Einzelheiten. Es enthält bereits genug Details, um als Verkaufswerkzeug zu dienen. Am Anfang steht eine Zusammenfassung der wichtigsten Dinge, gefolgt von den Inhalten des High-Concept Dokuments – jedoch wesentlich genauer ausgeführt. Die Hintergrund-Geschichte zum Spiel wird ebenso genau erklärt wie die Handlung im Spiel, Charakter-Entwicklung und das zu erreichende Spielziel. Spezielles Augenmerk soll wiederum auf die Höhepunkte (Unique Selling Propositions) gelegt werden. Weiters legt das Game-Treatment das geplante Budget sowie einen Zeitplan für die Umsetzung dar. Auch das Entwicklungsteam wird beschrieben. Alles in allem kann und soll das Game- KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN 7 Treatment das Spiel in einem möglichst positiven Licht darstellen, um die Aufmerksamkeit potentieller Geldgeber zu erlangen. Das dritte und auch wichtigste Game-Design Dokument ist das GameDesign-Script. Auf 50 bis 200 Seiten wird jeder Aspekt des Spiels bis ins kleinste Detail definiert und beschrieben. Vorrangig dabei sind der genaue Programmablauf und das Gameplay. Alles, was der Spieler im Spiel erfährt und tun kann, muss erfasst werden. Alle möglichen Wege zum Ziel, müssen beschrieben werden. Alle Charaktere, alle Orte und Szenerien, alle Gegenstände und sonstigen Elemente der Spielwelt werden niedergeschrieben. Auch das komplette Regelwerk des Spiels muss darin enthalten sein. Ein gutes Design-Script ermöglicht es, das Spiel mit Papier und Bleistift zu spielen, ohne auf Ungereimtheiten zu stoßen. Details zum Inhalt der drei Dokumente und Informationen zum Thema Game-Design siehe [18]. 2.1.2 Game-Programming Nachdem die Design-Phase abgeschlossen ist und die Idee des Spiels in umfangreichen und detaillierten Dokumenten niedergeschrieben wurde, erfolgt die Umsetzung dieser Aufgaben. Bei der Entwicklung eines modernen Computerspiels bedeutet dies jedoch nicht, dass auf dieser Basis sofort Code geschrieben wird. Vielmehr ist noch ein weiterer Planungsschritt notwendig, bevor mit der richtigen Umsetzung begonnen werden kann: Das ArchitekturDesign. Es beschreibt, aus welchen Teilen das gesamte Spiel besteht und wie diese zusammenhängen und -arbeiten. Dabei werden die Elemente aus dem Game-Design auf technische Gegebenheiten abgebildet. Das Ergebnis dieser Abbildung ist eine Hierarchie, die sich immer feiner aufteilen lässt, je tiefer man hinabsteigt. So steht an ihrer Spitze etwa das Spiel als großes Ganzes. Die nächste Ebene wird durch die Blöcke Grafik, Sound, Netzwerk, Physik und künstliche Intelligenz gebildet. Grafik etwa kann weiter in statische Objekte und bewegte Objekte unterteilt werden. Wie die Hierarchie eines Architektur-Dokuments aufgebaut wird, obliegt dem jeweiligen Team, welches diese konzipiert und auch umsetzt. Es gibt hierfür keine allgemein gültigen Regeln. Viele nützliche Tipps und Erfahrungen in diesem Bereich finden sich jedoch in [19]. Nachdem die Architektur des Spiels definiert wurde, muss noch die Technologie zur Umsetzung festgelegt werden. Dies betrifft die sehr hohen und abstrakten Schichten der Architektur-Hierarchie wie Grafik, Sound, usw. In großen Projekten werden diese Teile oft selbst umgesetzt, d. h., etwa GrafikEngines selbst entwickelt, um sie genauestens auf die Anforderungen des Spiels ausrichten zu können. Für andere, meist kleinere Projekte, wird zu diesem Zeitpunkt Research betrieben, um die bestmöglichen Komponenten für das Spiel zu finden. Die Auswahl der Technologie hängt dabei vor KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN 8 allem vom Typ des Spiels und seinem Gameplay ab. Ein klassischer FirstPerson-Shooter benötigt eine Grafik-Engine, die möglichst effizient Levels in geschlossenen Räumen darstellen kann. Für ein Spiel im freien Weltraum hingegen gelten natürlich völlig andere Anforderungen. Schließlich kommen noch finanzielle Aspekte hinzu, wenn es um die Auswahl und den Zukauf von externen Produkten geht. Ist die Architektur festgelegt und sind die verwendeten Technologien ausgewählt, kann die eigentliche Umsetzung beginnen. Da diese bei größeren Projekten fast ausnahmslos in einem Team abläuft, sind auch hier gewisse Regeln und Voraussetzungen notwendig, um einen effizienten Ablauf zu ermöglichen. Vorweg sei angenommen, dass alle Mitglieder des Entwicklungsteams das Programmieren, also die Fähigkeit, Probleme und Aufgabenstellungen algorithmisch zu lösen, beherrschen. Weiters sei angenommen, dass auch die verwendete Programmiersprache (Java, C++, usw.) beherrscht wird. Zu den wichtigsten Dingen beim Game-Programming zählt die Sourcecode-Qualität. Nur sauber geschriebener und einheitlicher Code garantiert ein qualitativ hochwertiges Endprodukt. Um dies zu erreichen sollten Code-Richtlinien verwendet werden, die eine durchgehende Formatierung festlegen. Neben der Qualität des Codes ist es auch wichtig, bestimmte Prioritäten für diesen festzulegen. Solche können Geschwindigkeit, Größe, Flexibilität, Portabilität oder Wartbarkeit sein. Je nach Projekt werden bestimmte Prioritäten vorausgesetzt bzw. forciert. Sie können sich aber auch je nach Projektphase ändern. Trotz bester Architektur und umfangreicher Richtlinien passieren in der Programmierung natürlich noch Fehler. Um jene frühzeitig zu erkennen, ist ein ausgewogener Zyklus bestehend aus Code-Schreiben und Code-Testen notwendig. Um einzelne Module auf Fehler zu testen, eignen sich so genannte Unit-Tests. Diese sind zwar aufwändig zu erstellen, zahlen sich trotzdem oft aus, da dem Entwickler eine langwierige Fehlersuche erspart bleibt. Grundsätzlich sollten fertige Code-Stücke sofort getestet werden. Oft stehen diese aber in unzähligen Abhängigkeiten, die ein solches Vorhaben erschweren. Wirft man nun einen Blick auf den aktuellen Abschnitt, so erkennt man sehr schnell, dass der Bereich des Game-Programmings nichts anderes ist als konventionelle Software-Entwicklung. Es erfordert viel Disziplin und ist meist nicht so spektakulär, wie es der Ausdruck Game-Programming“ zu” nächst vermuten lässt. Dennoch ist es der Teil der Spiele-Entwicklung, der die meisten Ressourcen (Zeit, Personen, Kapital) verschlingt und deswegen keinesfalls auf die leichte Schulter genommen werden sollte. In diesem Zusammenhang sei erneut auf [19] verwiesen, aus dem gute Tipps und Anregungen für den gegenwärtigen Abschnitt stammen. Am Ende dieses Abschnitts sei noch erwähnt, dass die Überblicke über Game-Design und Game-Programming natürlich nicht die einzigen zwei Teilbereiche der Spiele-Entwicklung sind. Auch besteht das Entwicklungsteam nicht nur aus Designern und Entwicklern. Vielmehr sind an der Entwicklung KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN 9 eines Spiels wesentlich mehr Personen beteiligt, als hier erwähnt wurden. So gibt es etwa Studio Manager, Producer, Project Manager, Product Manager, Quality Assurance Manager, Tester oder Sound Artists, um nur einige typische Berufsbilder zu nennen. 2.2 Grafik-Schnittstellen und Engines Das Ziel dieser Arbeit ist es, einen kurzen Überblick über Spiele-Entwicklung im Allgemeinen und detailliertere, implementierungsspezifische Ausführungen für die Umsetzung mittels Java zu geben. Wie bereits in den beiden vorhergehenden Abschnitten erklärt, ist Spiele-Entwicklung nicht ein einfaches Herunterprogrammieren“ von Ideen aus dem Kopf. Genauso wenig besteht ” Spiele-Entwicklung nur aus der grafischen Darstellung eines Spiels. Ein Spiel entsteht vielmehr erst durch das Zusammenspiel von Grafik, Sound, Physik und auch künstlicher Intelligenz. Dennoch liegt im Zuge dieser Arbeit und auch des hier ab Kapitel 4 vorgestellten Projekts der Schwerpunkt auf der grafischen Umsetzung. Die Gründe hierfür finden sich ebenfalls in einem späteren Kapitel dieser Arbeit. Die folgenden beiden Abschnitte über die zwei wichtigsten Grafik APIs (Application Programming Interfaces) und einige so genannte Middleware-Produkte sollen dazu dienen, vorab zu erklären, wie die grafische Darstellung von 3D Daten vonstatten geht. 2.2.1 OpenGL Eine sehr nüchterne und allgemeine Definition von OpenGL (kurz für Open Graphics Library), die jedoch auch im Zusammenhang mit Direct3D verwendet werden kann, lautet Software Schnittstelle für Grafik-Hardware“. ” Obwohl diese Definition zu generell ist, um sinnvoll verwendet werden zu können, zeigt sie jedoch gleich auf, was OpenGL nicht ist: Eine eigene (Programmier) Sprache. Vielmehr es eine Ansammlung aus über 250 Befehlen, die es erlauben, komplexe Szenen aus einfachen Grundprimitiven zu erstellen. OpenGL ist im eigentlichen Sinne nur eine Spezifikation jener Befehle, die genau definiert, welches Verhalten diese auszulösen haben. Auf der Basis dieser Spezifikation gibt es für viele verschiedene Plattformen Bibliotheken, die diese so hardwarenahe wie möglich implementieren. OpenGL ist also ein prozedurales API, im Gegensatz zu einem beschreibenden (wie etwa Java 3D). Das bedeutet, dass der Befehlssatz benützt wird, um die Schritte zu beschreiben, die notwendig sind, um eine Szene oder einen bestimmten Effekt darzustellen. Ein deskriptives API hingegen beschreibt direkt, wie die fertige Szene aussehen soll, und erledigt die dazu notwendigen Schritte im Hintergrund. Aus diesem Grund spricht man im Zusammenhang mit OpenGL auch von einem so genannten low-level API, da jeder einzelne Schritt des Renderings (der Darstellung) selbst definiert werden muss. KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN 10 Die OpenGL Bibliothek ist als Zustandsautomat konzipiert. Das heißt, dass eine bestimmte Eigenschaft einmal aktiviert wird und danach so lange gilt, bis sie explizit wieder deaktiviert wird. Auf diese Art und Weise ist es etwa möglich, eine große Anzahl von Vertices in der gleichen Farbe darzustellen, ohne bei jedem Zeichenaufruf für einen Vertex diese explizit mitgeben zu müssen. Die Versionspolitik von OpenGL hat gezeigt, dass neue Versionen oft in mehrjährigen Abschnitten veröffentlicht werden. Seine Aktualität behält das API durch die Möglichkeit der OpenGL Extensions (Erweiterungen). Diese werden in der Regel von Hardware-Herstellern veröffentlicht und ermöglichen, stets die neuesten Funktionalitäten diverser Grafik-Hardware zu verwenden. Die Erweiterungen sind zunächst meist proprietär, d. h. für das Produkt eines Grafikkarten-Herstellers zugeschnitten. Im weiteren Verlauf werden sie jedoch oft vom Architecture Review Board (ARB), einem Konsortium bestehend aus bedeutenden Firmen der Branche, standardisiert und schließlich im Zuge von neuen OpenGL Versionen in den StandardFunktionssatz übernommen. Die aktuell verfügbare Version ist OpenGL 2.0. Sie wurde Ende 2004 veröffentlicht und gilt als großer Schritt durch die Integrierung der OpenGL Shading Language. OpenGL ist rein funktional aufgebaut und für die Verwendung mit den Programmiersprachen C und C++ ausgelegt. Aufgrund der großen Verbreitung gibt es allerdings eine große Anzahl an Anbindungen für andere Sprachen wie etwa auch Java. Siehe dazu Abschnitt 3.1.2. OpenGL findet vor allem im professionellen 3D Grafik-Bereich Anwendung. Hauptgründe hierfür sind die offene, firmenungebundene Spezifikation und die Plattformunabhängigkeit. Im Spielebereich jedoch dominiert Direct3D den Markt. Viele Spiele verwenden ausnahmslos Direct3D als Renderer. Dies ist hauptsächlich damit zu begründen, dass Plattformunabhängigkeit im Spiele-Sektor nur eine untergeordnete Rolle spielt, da fast das gesamte Geschäftsergebnis auf der Windows-Plattform erzielt wird. Selbstverständlich gibt es auch Spiele bzw. Grafik-Engines, die beide APIs verwenden. Reine OpenGL basierte Produkte sind selten. Eine Ausnahme liefert die Firma id Software, deren Quake II Engine die erste war, die neben einem traditionellen Software-Renderer die Möglichkeit der hardwareunterstützten Darstellung bot. id Software setzte dabei ausschließlich auf OpenGL. 2.2.2 Direct3D Direct3D ist eine Schnittstelle (API) zur Programmierung von 3D-Grafiken. Es ist Bestandteil von DirectX Graphics, welches als Überbegriff für die 2D- und 3D-Grafik-Bibliotheken innerhalb des gesamten DirectX Frameworks gilt. Direct3D baut, ähnlich wie OpenGL, komplexe Szenen durch einfache Primitive auf. Wie auch bei OpenGL wird ein prozeduraler Ansatz verwendet, weswegen auch hier von einem low-level API gesprochen wird. KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN 11 Zusätzlich gibt es jedoch mit Direct3DX ein high-level API, welches auf Direct3D aufbaut und es ermöglicht, simple 3D-Szenerien schnell und einfach zu erstellen. Die Organisation von Direct3D (und auch DirectX im Allgemeinen) wird durch das Component Object Model (abgekürzt COM, eine Technologie zur Wiederverwendung von Software-Komponenten) vorgegeben. Während unter OpenGL alle Funktionen mehr oder weniger lose organisiert sind, erfolgt in Direct3D eine Aufteilung in verschiedene Schnittstellen (Interfaces). Diese sind von einem Basis-Interface (IUnknown) abgeleitet und fügen sich somit in eine hierarchische Einteilung. Die Schnittstellen und Datentypen in Direct3D sind also Klassen, deren Methoden die API-Funktionalitäten bereitstellen. Somit unterscheidet sich Direct3D durch seinen objektorientierten Ansatz erheblich von OpenGL. Die Voraussetzung dafür, dass Grafik-Hardware in Direct3D angesprochen werden kann, ist, dass der Treiber Direct3D kompatibel ist. Die verwendete Hardware wird explizit als Gerät im Code angelegt und unterstützte Funktionalitäten können darauf abgefragt werden. Erfolgt nun ein Aufruf, der von der Grafikkarte nicht unterstützt wird, so wird ein Fehler zurück geliefert. Hier unterscheidet sich Direct3D wiederum von OpenGL, das die darunter liegende Hardware völlig abstrahiert und im Falle von nicht unterstützten Funktionalitäten auch versucht, diese in Software nachzubilden. Direct3D überlässt diesen so genannten Fallback“-Mechanismus dem ” Applikations-Entwickler. Neue Funktionen werden in Direct3D nur in neuen Versionen integriert. Ein Extension-System, wie es in OpenGL zu finden ist, gibt es nicht. Für die Spiele-Entwicklung bedeutet dies, dass immer mit der neuesten Version gearbeitet werden sollte, um alle Möglichkeiten des APIs ausnützen zu können. Probleme können entstehen, falls während des Entwicklungsprozesses eine neue Version des Direct3D APIs veröffentlicht wird. Da das API, wie sich über die vergangenen Versionen gezeigt hat, immer wieder gröberen Änderungen unterzogen wird, sind bei der Spiele-Entwicklung mitunter große Modifikationen am Quellcode nötig, um eine Anpassung an eine neue Direct3D Version vornehmen zu können. Zum gegenwärtigen Zeitpunkt ist Version 9.0c aktuell, Version 10.0 soll 2007 mit der Veröffentlichung von Windows Vista erscheinen. Wie bereits erwähnt, ist Direct3D mittlerweile am Spiele-Sektor vorherrschend. Zu der großen Verbreitung auf der Windows Plattform kommt noch der Einsatz auf der XBox und der XBox 360 hinzu. Die dort verwendeten Direct3D Versionen unterscheiden sich zwar etwas von der Windows-Version, tragen jedoch nichtsdestotrotz zur weiten Verbreitung dieser Schnittstelle bei. Für die Entwicklung von Computerspielen mit Direct3D bzw. DirectX sei auf [20] verwiesen. KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN 2.2.3 12 Middleware Der Begriff Middleware wird in der Spiele-Branche für Produkte verwendet, die bestimmte Elemente des Spiele-Entwicklungsprozesses kapseln und fertig zur Verfügung stellen. So gibt es etwa Grafik-Middleware, die es erlaubt, 3D-Szenen auf höherer Ebene (z. B. deskriptiv in einem Szenengraphen) zu erstellen, anstatt mit einem prozeduralen low-level API wie OpenGL oder Direct3D zu arbeiten. Der Entwickler verwendet dabei die Funktionen der Middleware, welche dann intern die Darstellung wiederum mit OpenGL oder Direct3D vornimmt. Wie dies genau geschieht, ist für den Entwickler meist gar nicht von Bedeutung. Middleware gibt es aber nicht nur im grafischen Bereich. Es existieren Lösungen für alle Teilbereiche der Spiele-Entwicklung wie Physik, Netzwerk, künstliche Intelligenz oder Audio. Oft wird der Begriff auch mit dem Ausdruck Game-Engine“ gleichgesetzt. In diesem Fall bietet die Middleware ” alle zuerst genannten Funktionalitäten an. Typische Vertreter im grafischen Middleware-Bereich sind etwa OGRE 3D 1 oder Gamebryo 2 . Vollständige Middleware-Pakete (also Game Engines) sind z. B. RenderWare 3 oder Crystal Space 3D 4 . Auch abseits dieser klassischen Bereiche hat sich Middleware etabliert. So gibt es mit SpeedTree 5 ein Software-Paket, das sich nur um die Berechnung von möglichst realistischen Wäldern in Echtzeit kümmert. Die Aufgabe von Middleware ist es also, die Realisierung von Teilen eines Spiels zu vereinfachen und zu beschleunigen. Ein oft genanntes Argument für deren Verwendung ist, dass nicht bei jeder Spiele-Entwicklung eine Neuerfindung des Rades notwendig sei. Weiters sind Middleware-Produkte oft mehrere Jahre bereits am Markt und daher robust und ausgiebig getestet. Durch Middleware können also im Entwicklungsprozess deutlich Ressourcen gespart werden. Java 3D wird, um dies bereits hier vorweg zu nehmen, im Allgemeinen nicht als Middleware bezeichnet. Zwar handelt es sich um ein high-level API, dessen grafische Darstellung auf low-level APIs basiert, jedoch fehlt Java 3D eine durchgehende Tool-Chain (Werkzeug-Kette) zur Integrierung von diversen Medien. So gibt es etwa diverse Zusatztools wie Exporter aus 3D-Programmen, die bei typischen Middleware-Produkten stets vorhanden sind, nicht. 1 http://www.ogre3d.org/ http://www.emergent.net/index.php?source=gamebryo 3 http://www.renderware.com/ 4 http://www.crystalspace3d.org/ 5 http://www.speedtree.com/ 2 KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN 2.3 13 Wichtige Konzepte Kaum ein anderer Wirtschaftszweig, vielleicht abgesehen vom Militär, beeinflusst die Computerhardware-Industrie so stark bezüglich seiner Anforderungen wie die Spiele-Branche. Obwohl jene immer schneller mit neuen Generationen von Prozessoren und Grafikbeschleunigern aufwartet, gibt es immer wieder Spiele, für die es zum Zeitpunkt ihrer Veröffentlichung keine adäquate Hardware gibt. Gründe dafür sind neben einer immer detaillierteren Grafik auch die Vielzahl von Effekten. Solche kommen heutzutage in Computerspielen zum Einsatz, um eine grafische Darstellung zu ermöglichen, die nicht nur am neuesten Stand ist, sondern vor allem potentielle Käufer beeindruckt. Benötigt diese jedoch Hardware, die ein Großteil dieser Käufer nicht zur Verfügung hat, so hat das wiederum negative Auswirkungen. Wie also bringt man eine hohe Polygonanzahl mit einer Vielzahl an Effekten so unter, dass das Ergebnis sowohl performant funktioniert, als auch intern noch strukturiert und überschaubar bleibt? Im Laufe der Zeit wurden auf diesem Gebiet einige Konzepte entwickelt, die sich mittlerweile als Standards etabliert haben und in vielen Spielen zum Einsatz kommen. 2.3.1 Szenengraphen Wie bereits in Abschnitt 2.2 erwähnt, gibt es bei der Beschreibung einer 3D-Szenerie zwei verschiedene Ansätze: den prozeduralen und den deskriptiven. Ersterer beschreibt die notwendigen Schritte, die gebraucht werden, um die Szene darzustellen. Diese Schritte werden dann mit Funktionen des verwendeten 3D-APIs ausgeführt. Der zweite Ansatz beschreibt direkt, wie die Szene aussieht, also wo welche Objekte positioniert sind und welche Eigenschaften sie haben. Wird der beschreibende Ansatz gewählt, um eine 3D-Szene zu erzeugen, so wird bei steigender Komplexität der Szene die Notwendigkeit entstehen, diese in irgend einer Form zu organisieren, um die Daten überschaubar zu halten. Ein einfaches Beispiel: Ziel sei es, unser Sonnensystem für ein kleines Spiel in 3D nachzubauen. Benötigt wird dafür Geometrie für die Sonne, die neun Planeten und deren Monde. Einfache Kugeln in den entsprechenden Größen sind dafür ausreichend. Weiters haben die Sonne und die jeweiligen Planeten ihre unterschiedlichen Texturen. Für die Monde wird lediglich eine Textur verwendet. Die Planeten sollen nun in Umlaufbahnen um die Sonne kreisen, die Monde kreisen wiederum um die jeweiligen Planeten. Ein Ansatz, diese Aufgabenstellung zu lösen, wäre, alle Objekte eigenständig und voneinander unabhängig in der Szene anzuordnen. So würde man die Sonne im Mittelpunkt platzieren und die Planeten durch eine Rotation um jenen Mittelpunkt und eine anschließende Translation jeweils pro KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN 14 dargestelltem Frame an die richtige Position setzen. Dies könnte noch ohne großen Aufwand geschehen. Komplizierter würde es, wenn man nun die Position der Monde, die um einen der Planeten kreisen, bestimmen wollte. Da sie relativ zu den Positionen der Planeten wäre, müssten jene zuerst bekannt sein. Ginge man aber, wie zuerst angenommen, davon aus, dass alle Objekte völlig unabhängig voneinander existieren, würde man spätestens jetzt an einem Punkt ankommen, wo man diesen Ansatz schnell wieder verwerfen würde. Würde man die Vorbedingungen lockern und davon ausgehen, dass die Objekte zwar nach wie vor getrennt voneinander zu betrachten sind, Informationen über ihre Position, Rotation und Skalierung jedoch verfügbar sind, so bedeutete dies, dass es zwar möglich wäre, die durch die Rotation entstandene Position der Monde zu berechnen, dennoch wäre auch diese Lösung immer noch nicht optimal: Man nehme etwa an, dass nun auch auch Satelliten in das Modell des Sonnensystems einbezogen würden. Ein solcher könnte die Erde etwa in einer geosynchronen Umlaufbahn umkreisen, das hieße, er würde die Erde in der selben Zeitdauer umlaufen, die die Erde für eine Drehung um ihre eigene Achse benötigt. Als eigenständiges Objekt müsste man diesen Satelliten jeweils separat rotieren, obwohl er im Bezug auf die Erde seine Position nicht verändert. Spätestens an diesem Beispiel ist erkennbar, dass eine hierarchische Gliederung einer solchen Szene durchaus Sinn macht. Man spricht hierbei von einem Szenengraphen. Dabei handelt es sich um eine Baumstruktur mit einem Wurzelknoten, an dem so genannte Kindknoten hängen. Diese Kindknoten sind die Objekte einer Szene. Die einzelnen Kindknoten können sich nun wieder weiter verzweigen und bilden so die gewünschte Hierarchie. Diese stellt die logische Repräsentation der Szene dar. Der große Vorteil einer solchen Darstellung ist, dass die Änderung einer bestimmten Eigenschaft eines Knotens Auswirkungen auf alle darunter liegenden Kindknoten hat. So muss sie nur einmal ausgeführt werden und nicht für jedes Objekt separat. Verwendete man im zuvor vorgestellten Beispiel einen Szenengraphen, so ergäbe sich folgendes Szenario: Der Wurzelknoten des Szenengraphen wäre das Universum. Innerhalb dieses Universums würde die Sonne an einer bestimmten Stelle positioniert werden. Sie wäre somit der erste Kindknoten des Universums. Die Sonne ihrerseits hätte wiederum neun Kindknoten – die Planeten. Jeder der Planeten hätte seinerseits als Kindknoten die jeweiligen Monde und eventuelle Satelliten, die durch Translationen auf ihre Umlaufbahnen gebracht würden. Rotierte man einen Planeten um die Sonne, so würden seine Monde und Satelliten automatisch diese Rotation mitmachen. Im Falle des zuvor angesprochenen geosynchronen Satelliten wäre die Arbeit jetzt schon erledigt. Im Falle der Monde müsste lediglich noch eine Rotation hinzugefügt werden, damit sie um ihren jeweiligen Planeten kreisten (Abbildung 2.1). Der Szenengraph in diesem Beispiel dient nicht nur zur logischen sondern auch zur räumlichen Repräsentation der Objekte. Dies ist jedoch nicht zwin- KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN 15 Abbildung 2.1: Der Szenengraph des Planeten-Beispiels. Lediglich bei der Erde wurden die weiteren Kindknoten berücksichtigt. Die roten Knoten stellen die Transformationen dar, die auf die darunterliegenden Elemente wirken. R steht dabei für Rotation, T für Translation. gend notwendig. Wie das Konzept des Szenengraphen verwendet wird, ist im Regelfall von der Anwendung abhängig, die einen solchen implementiert. Es gibt daher auch keine strengen oder fixen Regeln, wie ein Szenengraph genau auszusehen hat und welche Daten er verwaltet. Die Vorteile des Szenengraphen liegen jedoch nicht nur in seiner Struktur, er kann auch zur Speicherersparnis in Spielen beitragen. Man denke etwa an ein Rollenspiel mit ausgedehnten Wäldern: In diesen Wäldern soll sich eine große Anzahl Wölfe befinden. Da diese in der Regel alle mit dem selben Modell und den selben Texturen dargestellt werden, ist es möglich, ein Objekt eines Wolfs anzulegen und wann immer ein Wolf in der Szene gebraucht wird, nur eine Referenz auf das bereits vorhandene Tier in den Szenengraphen zu integrieren. So wird die unnötige Duplizierung von Daten verhindert, was einen positiven Effekt auf die Speicherlast und die Geschwindigkeit hat. Bekannte Szenengraphen-Systeme sind Open Inventor 6 und OpenSG7 . Auch Java 3D verwendet Szenengraphen. 6 7 http://oss.sgi.com/projects/inventor/ http://www.opensg.org/ KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN 2.3.2 16 Scene-Management Performance und Geschwindigkeit sind wichtige Erfolgskriterien bei der Entwicklung eines Computerspiels. Ein langsames und schlecht lauffähiges Produkt wird am Markt genauso versagen wie ein Spiel mit veralteter Grafik. Performance und neueste Grafik-Funktionalitäten sind jedoch im Grunde betrachtet zwei sich ausschließende Dinge. Je detaillierter eine Szene ist (d. h. aus je mehr Polygonen sie besteht), desto mehr wird der Grafik-Hardware abverlangt. Würde man eine komplexe Szene mit allen Details komplett darstellen (d. h. jedes einzelne Polygon berechnen), würden auch Grafikkarten der aktuellsten Generation versagen. Ganz zu schweigen von älteren Modellen. Aus diesem Grund ist es unerlässlich, bereits vor der eigentlichen Darstellung zu bestimmen, welche Teile der Szene dargestellt, bzw. berechnet werden müssen und welche nicht. Diesen Vorgang nennt man SceneManagement. Es gibt eine Reihe von Techniken, die unter Scene-Management fallen. Im Folgenden sollen die wichtigsten und bedeutendsten unter ihnen kurz erläutert werden. Eine umfassende Erklärung zum Thema Scene-Management findet sich in [25]. Das Thema BSP-Bäume wird explizit in Abschnitt 2.3.3 erläutert. Verschiedene Techniken des Scene-Managements beschäftigen sich zunächst mit der Entfernung von Polygonen, die in einer 3D-Szene nicht sichtbar sind. Ein Polygon wird als nicht sichtbar bezeichnet, wenn es nach der Projektion auf die zweidimensionale Bildfläche nicht erkennbar ist. Dabei gibt es drei verschiedene Anwendungsfälle: Polygone, die durch andere verdeckt werden: Dieser Fall wird Hidden Surface Removal oder auch Occlusion-Culling genannt. Hierbei wird getestet, ob ganze Objekte oder einzelne Polygone durch andere vom derzeitigen Betrachterstandpunkt verdeckt werden. Alle Polygone, die als verdeckt markiert werden, werden in der Darstellung nicht berechnet. Polygone, deren Rückseite sichtbar wäre: Da die Rückseiten von Polygonen immer von der Richtung des Betrachters abgewandt sind, gelten sie als nicht sichtbar und werden nicht dargestellt. Diesen Fall bezeichnet man auch als Backface-Culling oder Backface-Removal. Polygone, die nicht im Blickfeld liegen: Jeder Betrachter sieht Dinge innerhalb eines bestimmten Blickwinkels. Alle Objekte, die nicht innerhalb dieses Blickfeldes (engl. View-Frustum) liegen, sind nicht sichtbar und können im Vorhinein aussortiert werden. Man spricht in diesem Falle von View-Frustum-Culling. Werden diese Methoden der Geometrie-Reduzierung so wie hier beschrieben angewandt, bedeutet dies, dass jedes Objekt in der Szene vor dem Rendern durchgetestet werden muss. Sind genauere Ergebnisse gewünscht, muss KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN 17 Abbildung 2.2: Eine Fläche, die mit einem Quadtree aufgeteilt wurde. Die roten Punkte repräsentieren Objekte auf dieser Fläche. Die Aufteilung erfolgte so, dass pro Unterteilung (Knoten) nur ein Objekt bzw. ein Teil dessen enthalten ist. von Objektbasis auf Polygonbasis gewechselt werden und die obigen Tests müssen auf dieser Ebene durchgeführt werden. Dies erfüllt zwar im Endeffekt seinen Zweck, jedoch ist es wenig performant. Darum eignet sich auch hier, ähnlich wie in Abschnitt 2.3.1, ein hierarchischer Ansatz. Dieser hierarchische Ansatz wird durch Quadtrees bzw. Octrees verkörpert. Beide Verfahren funktionieren äquivalent. Der Quadtree wird für eine zweidimensionale Sichtbarkeitsüberprüfung verwendet, der Octree für eine dreidimensionale. In weiterer Folge beziehen sich alle Ausführungen aus Gründen der besseren Vorstellbarkeit auf den Quadtree und somit auf die zweidimensionale Repräsentation. Ein Quadtree ist eine hierarchische Baumstruktur, bei der ein Knoten immer genau vier Kinder hat. Auf dieser Basis kann eine Fläche rekursiv in jeweils vier gleich große Teile aufgespaltet werden. Die vier Flächen bilden dann die Kindsknoten des übergeordneten Wurzelknotens (der gesamten Fläche). So kann eine Fläche immer weiter und feiner unterteilt werden. Befinden sich auf der Fläche nun etwa Objekte, so kann man die Erstellung des Quadtrees so steuern, dass in jedem Knoten nur genau eines dieser Objekte vorhanden sein darf. Ist nur mehr ein Objekt vorhanden, erfolgt keine weitere Aufteilung, ansonsten wird so lange geteilt, bis die definierte Abbruchbedingung gilt. Während der Erstellung des Quadtrees wird in den Knoten gleich die Information mitgespeichert, welches Objekt sich in der dem Knoten zugeteilten Fläche befindet. Am Ende existiert eine Datenstruktur, die genaue Auskunft über die Position der darin enthaltenen Objekte gibt. Abbildung 2.2 zeigt die Aufteilung einer Fläche mit Hilfe eines Quadtrees. Somit kann etwa View-Frustum-Culling wesentlich effizienter ausgeführt KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN 18 werden. Anstatt jedes Mal alle Objekte in der Szene zu testen, überprüft man den Quadtree. Wenn etwa eine der vier obersten“ Flächen bzw. das ” darin enthaltene Objekt nicht mehr im Sichtfeld liegt, so müssen alle Objekte, die an Kindsknoten hängen, nicht mehr getestet werden. Neben dem Sichtbarkeitstest kann der existierende Quadtree zusätzlich auch zur Kollisionserkennung eingesetzt werden. Dazu muss lediglich überprüft werden, in welchem Quadranten (bzw. Knoten) sich der Betrachter befindet. In Folge muss dann nur getestet werden, ob dieser mit dem Objekt, das sich in diesem Quadranten befindet, kollidiert. Alle anderen Objekte in der Szene können außer Acht gelassen werden, bis der Betrachter in einen anderen Quadranten wechselt. Quadtrees dienen somit also vor allem zur Effizienzsteigerung der Sichtbarkeitsüberprüfung, werden jedoch auch auf anderen Gebieten wie z. B. der dynamischen Terrain-Generierung eingesetzt (siehe dazu [25]). Besinnt man sich noch einmal zurück auf die zuvor gegebene Definition des Scene-Managements, so geht es darum, nur Dinge zu berechnen, die unbedingt notwendig sind. Dies betrifft nicht nur die Behandlung von nicht sichtbaren Objekten, sondern auch von schlecht sichtbaren. Damit sind Objekte gemeint, die weit von der Position des Betrachters entfernt sind. Diese müssen im Allgemeinen nicht mit der vollen Anzahl an Polygonen dargestellt werden, da sie ohnehin nicht sichtbar wären. Daher kommt in diesem Fall das so genannte Level of Detail (LOD) zum Einsatz. Die einfachste Ausführung davon ist das so genannte Discrete Level of Detail. Hierbei wird ein 3D-Modell in mehreren Auflösungen erstellt. In nächster Nähe wird dabei das Modell mit der höchsten Polygon-Anzahl verwendet, um eine bestmögliche grafische Darstellung zu erreichen. Ab einer gewissen, voreingestellten Distanz wird auf das Modell mit der nächst geringeren Auflösung umgeschaltet usw. Dieser Ansatz ist leicht zu implementieren, hat jedoch den Nachteil, dass ein Mehraufwand bei der Generierung von Modellen entsteht und dass es beim Wechsel der Modelle zu so genanntem Popping kommt, welches im Allgemeinen als störend wahrgenommen wird. Um dies zu vermeiden, gibt es den Ansatz des Continuous Level of Detail, bei dem von einem hochauflösenden Modell ausgegangen und je nach Bedarf die Detailstufe algorithmisch verringert wird. Dieser Ansatz ist rechenaufwändiger und wird eher für großflächige Objekte verwendet. Continuous Level of Detail findet etwa Anwendung bei der Terraingenerierung mittels Quadtrees (siehe wiederum [25]). 2.3.3 BSP-Bäume Von allen Scene-Management-Techniken hat wohl der BSP-Baum (Binary Space Partitioning Tree) die größte Bedeutung für die Entwicklung von 3DSpielen. Durch den Einsatz eines BSP-Baumes gelang es der Firma id Software Anfang der 90er Jahre, Spiele wie Doom und in weiterer Folge Quake zu veröffentlichen, die für die damalige Zeit beinahe revolutionär waren, vor KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN (a) 19 (b) Abbildung 2.3: 3D-Geometrie, wie sie von einem BSP-Baum aufgeteilt wird. Bei der ersten Teilung (a). Nach der vollständigen Teilung (b). Entnommen aus [25]. allem was die Grafik betraf. Bei BSP-Bäumen handelt es sich erneut um eine hierarchische Baumstruktur. Jeder Knoten in dieser Struktur kann genau zwei Kinder haben. Das Besondere am BSP-Baum, verglichen etwa zum Quad- oder Octree ist die Möglichkeit, an den einzelnen Knoten jeweils die Geometrie mitzuspeichern. Der Wurzelknoten repräsentiert damit die gesamte Geometrie der Szene. Um die Szene in zwei Hälften aufzuteilen, wird ein Polygon gewählt, das eine Ebene erzeugt, welche die Geometrie so teilt, dass zwei konvexe Geometriehälften entstehen (Splitter-Polygon). Der Ast mit der Geometrie, die vor der Teilungsebene liegt, wird als Frontlist bezeichnet, die andere Hälfte als Backlist. Diese Teilung wird nun so lange rekursiv fortgesetzt, bis kein Polygon mehr gefunden werden kann, das eine Teilung in Frontund Backlist ermöglicht. Die so erhaltene Struktur enthält nun Geometrieteile, von denen bekannt ist, dass sie sich nicht gegenseitig verdecken können (da sie alle konvex sind). Da der BSP-Baum gleichzeitig auch nach Distanz sortiert ist, kann der Baum ohne Probleme durchlaufen werden und die einzelnen Teile von hinten nach vorne gezeichnet werden. Abbildung 2.3 zeigt Beispiel-Geometrie, die von einem BSP-Baum aufgeteilt wurde. Neben der Aufgabe, ein fehlerfreies Rendering zu ermöglichen, können BSP-Bäume auch für andere Zwecke verwendet werden. Beispiele sind Kollisionserkennung oder Sichtbarkeitsüberprüfung. Zerbst erklärt einige dieser Variationen in [25]. Die ersten Schritte in Richtung BSP-Baum wurden in [4] gemacht. Fuchs et. al. beschreiben darin eine Möglichkeit zur Prioritätsbestimmung von Polygonen in einer 3D-Szene. Dieser Algorithmus erlaubt es, für jede beliebige Geometrie eine Reihung der Polygone vorzunehmen. Je höher ein Polygon KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN 20 gereiht wird, desto geringer ist die Wahrscheinlichkeit, dass es von einem anderen verdeckt wird. In [5] stellen Fuchs et. al. schließlich das bereits oben beschriebene Konzept des BSP-Baumes vor. Besonderes Augenmerk wird hierbei vor allem auf die Phase der Bildgenerierung gelegt. Der BSP-Baum wird dabei so durchlaufen, dass wiederum eine Sortierung nach Prioritäten erfolgt und die Polygone mit der höchsten Priorität zuletzt gezeichnet werden. Dazu muss jeweils zuerst die Backlist besucht werden, dann der Knoten mit dem Splitter-Polygon und zuletzt die Frontlist. BSP-Bäume galten lange als das Maß der Dinge in der Spiele-Entwicklung. Die Entwicklung bei Grafik-Hardware, insbesondere die Einführung eines Tiefenbuffers haben jedoch Algorithmen zur Prioritätssortierung wie den BSP-Baum entbehrlich gemacht. In heutigen, zeitgemäßen Spielen kommt dieser Algorithmus daher immer seltener zur Anwendung. 2.4 Existierende Arbeiten auf diesem Gebiet Marner evaluiert in [15] die Tauglichkeit von Java für die Spiele-Entwicklung. Er beginnt nach einer kurzen Einleitung mit einem Kapitel über SpieleEntwicklung. Darin erläutert er die Definition von Computerspielen und gibt einen Überblick über die verschiedenen Plattformen. Er unterscheidet dabei zwischen PC, Konsolen, Arcade-Geräten, Web-basierten Spielen, tragbaren Konsolen, interaktivem Fernsehen, Set-top Boxen, Mobiltelefonen, tragbaren Geräten wie etwa Palm-Tops, Unterhaltungs-Kiosks und Slot-Maschinen. In weiterer Folge geht Marner auf die Geschäftsmodelle von PC-Spielen und Konsolen ein. Das nächste Kapitel widmet sich komplett der Programmiersprache Java. Zunächst wird der Ausdruck Java erläutert und eine Übersicht über die Funktionsweise des Java-Compilers und Interpreters gegeben. In weiterer Folge erläutert er das Konzept der Virtual Machine und geht dabei auch auf Just-in-Time Compiler und Hotspot-Kompilierung ein. Der restliche Teil des Kapitels widmet sich eher kurz den verschiedenen Java-Plattform-Editionen, Applets, der Standardbibliothek und dem Development Kit. Kapitel 5 enthält einen Vergleich zwischen Java und C++. Zuerst wird auf den Unterschied zwischen Applikations-Programmiersprache (Java) und System-Programmiersprache (C++) hingewiesen, im weiteren Verlauf werden die Themen Objektorientiertheit, Portabilität und Garbage-Collection (Aufräumen von nicht mehr benötigten Objekten im Speicher) behandelt. Ebenso werden die Unterschiede bei der Behandlung von Arrays, Referenzen und Pointern, der Typumwandlung und der Standardbibliothek und noch einige andere hervorgehoben. Das darauf folgende Kapitel widmet sich voll und ganz der Performance von Java und erklärt gleich vorweg, in welchen Situationen Java beson- KAPITEL 2. SPIELE-ENTWICKLUNG ALLGEMEIN 21 ders langsam ist und wie sich die Geschwindigkeit über die verschiedenen Versionen hinweg verbessert hat. Dabei wird auch auf die Unterschiede zwischen den verschiedenen Virtual Machines (von Sun, IBM, Microsoft, usw.) eingegangen. Marner erläutert in diesem Kapitel nochmal detailliert das Prinzip der Hotspot Technologie und geht auch auf Dinge wie statische Java-Compiler ein. Besonders hebt er die Notwendigkeit des so genannten Code-Tweakings hervor. Tweaking bedeutet im übertragenen Sinn an ” etwas basteln oder schrauben“, also alle möglichen Optimierungen durchzuführen. Im Falle des Code-Tweakings werden also vor allem jene Stellen optimiert, die oft ausgeführt werden und daher zeitkritisch sind. Mögliche einfache Tweaking-Methoden sind etwa die Vermeidung von unnötigen Objekt-Allokierungen (d. h. Wiederverwendung von bestehenden Objekten) oder die Vermeidung von unnötigen Methodenaufrufen. Methoden dieser Art reduzieren jedoch in sehr vielen Fällen die Lesbarkeit des Codes und sollten daher bedacht angewandt werden. Marner ist dennoch der Meinung, dass dies unbedingt notwendig sei, um schnellen Code zu erzeugen. In Kapitel 7 werden die drei Beispiel-Applikationen getestet, die im Rahmen dieses Berichtes entstanden: ein Partikelsystem, eine Implementierung des Game of Life, wo sich Zellen aufgrund bestimmter Verhältnisse entwickeln und eine OpenGL Grafik-Demo. Alle drei Applikationen wurden sowohl in Java als auch in C++ entwickelt und unter verschiedenen Bedingungen miteinander verglichen. Es zeigte sich, dass Java-Code im Normalfall langsamer als C++ Code war, teilweise konnte jedoch speziell verbesserter Java-Code eine optimalere Leistung erzielen. Marner schließt seinen Report mit einer Evaluierung von Java für den Spiele-Sektor ab. Er stellt darin fest, dass Programmieren in Java wesentlich produktiver sei und zählt noch weitere Vorteile von Java auf. Solche sind etwa Stabilität, gute Dokumentation, guter Support oder geringe Migrationskosten. Als Nachteile listet er geringe Code-Sicherheit und Probleme bei der Auslieferung von Java-Applikationen auf, bietet jedoch adäquate Lösungen an. Er geht weiters auf die problemlose Einbindung von OpenGL, DirectX sowie die Verwendung von Java 3D ein. Im letzten Teil des Kapitels listet Marner Möglichkeiten für die Verwendung von Java in Spielen auf. Diese erstrecken sich seiner Ansicht nach von der puren Java-Applikation bis hin zur Möglichkeit, Java als Skriptsprache einzusetzen. Weiters zählt er noch einige Java-Spiele auf und beschäftigt sich mit Portabilität und Performance. In seinem Fazit stellt Marner Java generell ein gutes Zeugnis für die Spiele-Entwicklung aus, empfiehlt es jedoch nicht für perfomancekritische Spiele. Kapitel 3 Ansätze in Java 3.1 Grafik-Schnittstellen und Engines Aufbauend auf Abschnitt 2.2 sollen in diesem Kapitel einige Möglichkeiten vorgestellt werden, 3D-Szenen nun in Java umzusetzen. Dabei gibt es wiederum zwei verschiedene Ansätze: High-level Java APIs oder auch Middleware erlauben eine Erstellung der Szene mit einem Szenengraphen, identisch zu den im letzten Kapitel vorgestellten Methoden. Das Rendering wird intern von OpenGL oder Direct3D vorgenommen. So genannte Java-Bindings (Anbindungen) hingegen ermöglichen ein Arbeiten mit den low-level APIs OpenGL und Direct3D genau so wie in deren nativer Sprache C bzw. C++. Dies bedeutet, dass alle C-Funktionen als Java-Methoden zur Verfügung gestellt werden und auch komplett identisch verwendet werden können. 3.1.1 Java 3D Java 3D ist ein high-level API, das die Erstellung von komplexen 3D-Szenen mit Hilfe eines Szenengraphen ermöglicht. Die zur Zeit stabile Version ist 1.4, Version 1.5 ist bereits in Entwicklung und kann in regelmäßigen Abständen als so genannte Nightly-Builds (Versionen, die am letzten Stand der Entwicklung, jedoch meist ungetestet sind) bezogen werden. Die Entwicklung von Java 3D wird seit Version 1.3 nicht mehr von Sun Microsystems sondern durch den Java Community Process (JCP) vorangetrieben. Der JCP ist ein Standardisierungskomitee, das sich der Weiterentwicklung der Programmiersprache Java widmet. Neue Erweiterungen werden in so genannten Java Specification Requests (JSR) erfasst, erhalten eine fortlaufende Nummer und sind auf einer zentralen Website1 einsehbar. Auf diese Art und Weise ist es möglich, die Entwicklung neuer Funktionalitäten bereits in der Planungsphase auf eine breite öffentliche Basis zu stellen. Dieses Konzept wird dann durch eine Open-Source Realisierung fortgeführt. 1 http://www.jcp.org/ 22 KAPITEL 3. ANSÄTZE IN JAVA 23 Die Konzipierung von Java 3D 1.3 wurde im JSR 912 festgelegt, Java 3D 1.4 wird in JSR 926 behandelt. Wie bereits anfangs erwähnt, verwendet Java 3D einen Szenengraphen zur Organisation der darzustellenden Objekte. Dieser Graph kann neben 3D-Geometrie auch Materialien, Beleuchtung, Sounds (akustische Effekte) und das Betrachtermodell beinhalten. Die darunter liegende Grafik-Pipeline (der Weg vom logisch definierten Objekt bis zu seiner 2D-Projektion am Bildschirm) bleibt völlig verborgen. Die Darstellung (das Rendering) erfolgt wahlweise mittels OpenGL oder Direct3D. Ältere Versionen von Java 3D (vor Version 1.3.2) wurden noch getrennt als OpenGL- bzw. Direct3DVersion ausgeliefert. Mittlerweile sind beide Renderer integriert. Der Java 3D Szenengraph ist aus einzelnen Knoten aufgebaut. Diese werden durch die abstrakte Klasse javax.media.j3d.Node repräsentiert. Von ihr sind die zwei eigentlichen Knoten-Haupttypen, javax.media.j3d.Group und javax.media.j3d.Leaf, abgeleitet. Erstere dienen ganz allgemein zur Gruppierung von Elementen. Der Subtyp javax.media.j3d.BranchGroup fasst dabei Objekte im Szenengraph logisch zusammen und bildet somit eine eigene Verzweigung (einen so genannten Branch) in der Baumstruktur. Als Transformationsknoten dient der Typ javax.media.j3d.TransformGroup. D. h. alle Kinderknoten, die an ihm hängen, erfahren die auf den Knoten ausgeführten Transformationen (Translation, Rotation, Skalierung). So ist es möglich, mit der Angabe lediglich einer Transformation gleich eine größere Menge von Objekten zu beeinflussen. Neben diesen zwei wichtigen Gruppenknoten gibt es noch einige weitere wie etwa javax.media.j3d.OrderedGroup (alle Objekte an diesem Knoten werden in vorgegebener Reihenfolge gerendert), javax.media.j3d.Switch (erlaubt das selektive Rendern von Kindknoten) oder auch com.sun.j3d.utils.geometry.Primitive (die Basisklasse für alle Geometrieprimitive in Java 3D, deren Geometrie dadurch wiederverwendet wird). Leaf-Knoten hingegen haben keine Kinder und sind somit Endpunkte einer Verzweigung im Graphen. Sie sind die Knoten, die Szeneninformation beinhalten. Leaf-Knoten sind etwa javax.media.j3d.Shape3D (repräsentiert alle geometrischen Objekte in der Szene), javax.media.j3d.Light (stellt eine Lichtquelle dar), javax.media.j3d.Sound (Musik/Geräusche), javax.media.j3d.Behavior (zur Manipulation des Graphen zur Laufzeit aufgrund bestimmter Ereignisse wie z. B. Benutzer-Input), oder auch javax.media.j3d.ViewPlatform (repräsentiert die Position und Orientierung des Betrachters). Eine vollständige Auflistung aller Knotentypen findet sich in der API-Dokumentation zu Java 3D 2 . Jeder Java 3D Szenengraph ist in einer umgebenden Superstruktur, einem so genannten virtuellen Universum (repräsentiert durch die Java-Klasse 2 http://download.java.net/media/java3d/javadoc/1.4.0/ KAPITEL 3. ANSÄTZE IN JAVA 24 javax.media.j3d.VirtualUniverse) eingebunden. Dieses Universum enthält die ganze Szene und ist in seiner Ausdehnung prinzipiell unendlich. Um nun einen exakteren Raum für die Positionierung des Szenengraphen zu bestimmen, hat ein virtuelles Universum eines oder mehrere Objecte des Typs javax.media.j3d.Locale (zu Deutsch Schauplätze“). Dieser Raum defi” niert ein hochpräzises Koordinatensystem, repräsentiert durch vorzeichenbehaftete, zweierkomplementäre 256 Bit Fixkommawerte. An diesem LocaleKnoten teilt sich dann der Szenengraph in zwei Teile auf: In View-BranchGraph (Betrachterzweig) und Content-Branch-Graph (Inhaltszweig). Der View-Branch-Graph enthält die zuvor bei den Leaf Knoten bereits erwähnte Klasse javax.media.j3d.ViewPlatform. In Interaktion mit dieser steht javax.media.j3d.View, welche sozusagen die Art der Ausgabe definiert. Standardmäßig handelt es sich dabei um einen normalen Bildschirm. Java 3D unterstützt jedoch auch Head Mounted Displays (HMD oder auch 3D-Brille“ genannt). Da in den meisten Fällen der View-Branch für Betrach” ter, die einen Monitor verwenden, benötigt wird (d. h. Virtual Universe, Locale und View sind identisch oder zumindest ähnlich), gibt es eine existierende Klasse namens com.sun.j3d.utils.universe.SimpleUniverse, die all diese Dinge bereits beinhaltet. Im Regelfall kann auf diese Klasse zurückgegriffen werden. Auch die in Kapitel 4 beschriebene Applikation verwendet SimpleUniverse als Ausgangspunkt für die Erstellung des View-BranchGraphs. Der Content-Branch-Graph enthält schließlich noch die eigentliche Szene, die dargestellt werden soll: 3D-Geometrie, Lichter, Sound usw. Die Komplexität dieses Teils des Graphen hängt von der jeweiligen Applikation ab. Der Aufbau des Content-Branch der im Zuge dieser Arbeit entwickelten JSceneViewer3D Applikation wird in Kapitel 4 näher erklärt. Den prinzipiellen Aufbau eines einfachen Java 3D Anwendung zeigt Abbildung 3.1. Arten des Renderings Java 3D unterstützt drei unterschiedliche Arten des Renderings: Den Immediate Mode (direkter Modus), den Retained Mode (zurückhaltender Modus) und den Compiled Retained Mode (kompiliert zurückhaltender Modus). Alle drei Modi operieren auf der Klasse Canvas3D. Diese stellt die Zeichenfläche dar, in der die Darstellung der 3D-Szene erfolgt. Die Klasse Canvas3D ist eine AWT Component, d. h. sie kann problemlos in jedem AWT oder Swing GUI (Graphical User Interface) eingebettet werden. In diesem Falle spricht man dann von einer on-screen ( auf den Bildschirm“) Rendering-Klasse. ” Die Klasse kann dann entweder mit Single-Buffering (einem Zeichenpuffer) oder Double-Buffering (zwei Zeichenpuffern) bzw. monoskopisch (Einzelbilder werden erzeugt) oder stereoskopisch (Raumbilder werden erzeugt) verwendet werden. Wird Canvas3D nicht zusammen mit einem GUI verwendet, so agiert die Klasse als off-screen Puffer. Diese ermöglicht ein Rendering im KAPITEL 3. ANSÄTZE IN JAVA 25 Abbildung 3.1: Der Aufbau eines einfachen Java 3D Szenengraphen, Die Grafik wurde der in [2] nachemfpunden. Hintergrund. Die Daten können dann bei Bedarf an einen on-screen Puffer geschickt werden – so lässt sich etwa Double-Buffering selbst implementieren. Auch für Effekte wie Shadow-Mapping (ein Verfahren zur Erzeugung von Schatten) ist ein off-screen Puffer nötig. Dieser kann in Java 3D per Definition nur monoskopisch und mit Single-Buffering verwendet werden. Wirft man nun einen detaillierteren Blick auf die drei Rendering-Modi, so überlässt der Immediate Mode dem Entwickler die meisten Freiheiten, verlangt von ihm aber auch die Angabe von mehren Details bei der Realisierung der Applikation. So braucht der Entwickler zwar nicht unbedingt die Java 3D Szenengraphen-Struktur zu verwenden (lediglich der View-BranchGraph muss existieren), muss jedoch auch die Rendering-Schleife selbst starten und verwalten. Diese Art der Java 3D Darstellung geschieht also auf einer sehr niedrigen Ebene. Um den Immediate Mode zu verwenden, müssen die diversen Render-Methoden der Klasse Canvas3D vom Benutzer entsprechend überschrieben werden. Ein Beispiel in Pseudocode für die aufzubauende Rendering-Schleife gibt Abbildung 3.2. Details zu den zu überschreibenden Methoden finden sich in der Java 3D API-Dokumentation. Der Retained Mode erfordert die Verwendung des Szenengraphen. Alle Objekte werden genau in ihren Eigenschaften spezifiziert und sind dem Java 3D Renderer bekannt. Dieser kann dadurch beim Rendering Optimierungen durchführen, die im Immediate Mode nicht möglich sind. Ein wichtiger Teil dieser Optimierungen besteht darin, zu wissen, welche Elemente im Graphen sich ändern können und welche nicht. Jeder Knoten besitzt zu diesem Zweck KAPITEL 3. ANSÄTZE IN JAVA 26 clear canvas call preRender() set view branch render opaque scene graph objects call renderField(FIELD_ALL) render transparent scene graph objects call postRender() synchronize and swap buffers call postSwap() Abbildung 3.2: Der prinzipielle Aufbau des Renderings im Java 3D Immediate Mode. so genannte Capability-Bits (Fähigkeits-Bits), die festlegen, ob bestimmte Eigenschaften abgefragt und verändert werden können. Standardmäßig sind alle Bits, die das Lesen von Attributen bestimmen, auf true gesetzt. Alle Bits, die verantwortlich sind, ob eine Eigenschaft zur Laufzeit verändert werden darf, sind im Normalfall auf false gesetzt. Will man nun explizit eine Veränderung erlauben, so muss das Bit umgesetzt werden. Alle Dinge im Graphen, die sich nicht verändern, können somit leicht für Optimierungen herangezogen werden. De Compiled Retained Mode arbeitet nach den selben Prinzipien wie der Compiled Mode. Der Unterschied besteht lediglich darin, dass am Ende der Szenen-Erzeugung vom Benutzer die Methode compile() auf die oberste BranchGroup aufgerufen wird. Dadurch wird der Szenengraph in eine interne, möglichst performante Struktur umgewandelt. Es ist auch möglich, dass mehrere einzelne Objekte im Szenengraphen zu einem neuen Objekt vereint werden oder dass Geometrie komprimiert wird. Generell gibt es für die meisten Anwendungen keinen Grund, nicht den Compiled Retained Mode zu verwenden. Er bietet aufgrund des Szenengraphen die komfortabelste Art, eine Szene zu erstellen und zu verwalten. Durch die von Java 3D vorgenommenen Optimierungen ist es auch für unerfahrenere Entwickler möglich, Performance-Steigerungen zu erhalten. KAPITEL 3. ANSÄTZE IN JAVA 3.1.2 27 OpenGL-Anbindungen für Java GL4Java GL4Java3 ist eine der ersten vollständigen OpenGL Anbindungen für Java. Zwar gab es vor 1997, als das Projekt startete, schon erste Ansätze, die sich dem Thema widmeten, diese waren jedoch im Funktionsumfang nicht komplett. GL4Java bildete als erste Anbindung den vollen OpenGL und GLU (OpenGL Utility Library) Befehlssatz in Java ab. Mittlerweile wird an dem Projekt nicht mehr aktiv gearbeitet. Die letzte verfügbare Version ist 2.8.0.8. Diese bildet OpenGL und GLU Version 1.2 auf Java ab. GL4Java besteht aus zwei Teilen: Den Java-Klassen, die als JAR (Java Archive) Dateien vorliegen und alle OpenGL und GLU Befehle beinhalten, sowie den Systembibliotheken. Während die Java-Klassen systemunabhängig sind, werden die Systembibliotheken für das jeweilige Zielsystem benötigt. Unterstützt werden Windows und Linux Plattformen. Methodenaufrufe in den GL4Java-Klassen werden über das Java Native Interface (JNI) an die Systembibliotheken weitergeleitet. Diese wiederum rufen die jeweiligen im System ebenfalls durch Bibliotheken verankerten OpenGL Funktionen auf. Der Aufbau einer GL4Java-Applikation beginnt mit der Erstellung der Hauptklasse, welche entweder von der Klasse gl4java.awt.GLCanvas oder von gl4java.awt.GLAnimCanvas (letztere bietet Unterstützung für die Verwendung Threads) abgeleitet werden muss. Diese Basisklasse beinhaltet die von OpenGL bekannten Rendering Methoden display() (zum Rendern der Szene), reshape() (zur Anpassung der Anzeige, falls das Fenster verändert wird) und auch init() (zur Initialisierung der Szene). Eine Verwendung des GLCanvas mit AWT oder auch Swing bietet sich an. Die Basisklasse stellt Objekte der Schnittstellen-Typen GLFunc und GLUFunc zur Verfügung. Über diese Objekte können alle OpenGL- und GLU -Funktionen aufgerufen werden. Eine weitere Hilfsklasse bietet alle Enumerations-Typen bzw. OpenGLDatentypen an. JOGL JOGL4 ist eine aktuelle, in Entwicklung befindliche OpenGL Anbindung für Java. Das Projekt unterliegt gleich wie Java 3D dem Java Community Process und ist im JSR 231 erfasst. Dies ist die formale Spezifikation für eine Java OpenGL Anbindung. JOGL ist dafür die Referenz-Implementierung. Im Folgenden wird der Begriff JOGL aus Einfachheitsgründen umfassend sowohl für die Spezifikation als auch für die existierende Implementierung verwendet. Aufbau und Versionierung von JOGL wurden anfänglich noch nicht gemäß JSR 231 vorgenommen, weshalb die ersten JOGL Versionen noch unter dem Namen JOGL 1.0“ veröffentlicht wurden. Ab Oktober 2005 ” 3 4 http://gl4java.sourceforge.net/ https://jogl.dev.java.net/ KAPITEL 3. ANSÄTZE IN JAVA 28 wurde die JOGL API an die Erfordernisse der JSR 231 angepasst. Aktuelle Versionen werden daher als JSR 231 Beta“ herausgegeben. Zum gegebe” nen Zeitpunkt ist Beta 4 die aktuellste Implementierung. Sie unterstützt OpenGL 2.0, CG (eine Shader Sprache), GLU 1.3 und bietet auch Funktionen des GLUT (OpenGL Utility Toolkit). JOGL verfolgt die Struktur betreffend den selben Ansatz wie GL4Java. Java-Klassen werden in Form einer JAR-Datei in den Klassenpfad eingebunden, wodurch die JOGL Klassen zur Verfügung stehen. Diese Klassen kommunizieren wiederum per JNI mit systemabhängigen Bibliotheken, die sich für die Umsetzung der OpenGL-Befehle verantwortlich zeichnen. Beim Aufbau von Applikationen unterscheidet sich JOGL jedoch von GL4Java. Die Anbindung stellt mit javax.media.opengl.GLCanvas und javax.media.opengl.GLJPanel sowohl eine AWT als auch eine Swing GUI Komponente bereit, in der die Darstellung erfolgen kann. Die eigentliche Programmklasse wird nicht von einer der beiden abgeleitet, sondern muss lediglich die Schnittstelle javax.media.opengl.GLEventListener implementieren. Diese stellt die Methoden init() (Initialisierung), display() (Darstellung), reshape() (Anpassung der Anzeige) und displayChanged() (wenn das Ziel des Renderings, etwa ein Puffer, verändert wird) zur Verfügung. Der Canvas-Instanz kann nun das Objekt, das den GLEventListener implementiert – also die Programmklasse – hinzugefügt werden. Somit werden die Events, die durch die Rendering-Schleife erzeugt werden, ausgegeben. Um nun auf den OpenGL Befehlssatz zugreifen zu können, liefern alle Methoden der GLEventListener Schnittstelle ein Objekt vom Typ javax.media.opengl.GLAutoDrawable als Parameter. Dieser Datentyp abstrahiert ein Rendering-Ziel (also z. B. einen on-screen Puffer) und liefert mittels der Methode getGL() alle Befehle, die auf jenem Rendering-Ziel ausgeführt werden können. Diese Befehle werden in einem Objekt vom Typ GL gekapselt. JOGL stellt darüber hinaus noch Hilfsklassen zur Verfügung, etwa zum Laden von TGA-Dateien, zum Anfertigen von Screenshots oder für die Ausgabe von OpenGL Debug Informationen. LWJGL LWJGL5 – kurz für Light Weight Java Game Library – kann ebenfalls als OpenGL-Anbindung für Java gesehen werden, obwohl das Projekt noch weitere Funktionalitäten bietet. So könnte LWJGL durchaus auch als Middleware bezeichnet werden, denn sie bietet neben der OpenGL-Anbindung noch Anbindungen für OpenAL (Open Audio Library)6 , FMOD7 (eine weitere Audiobibliothek) sowie DevIL8 (ein Textur Loader) und Hilfsklassen für die 5 http://www.lwjgl.org/ http://www.openal.org/ 7 http://www.fmod.org/ 8 http://openil.sourceforge.net/ 6 KAPITEL 3. ANSÄTZE IN JAVA 29 Verarbeitung von Eingabegeräten (Tastatur, Maus, Joystick) und das Laden von 3D-Modellen. Die aktuelle Version 0.99 von LWJGL unterstützt OpenGL 2.0. Auch der GLU Befehlssatz ist enthalten. LWJGL macht im Unterschied zu GL4Java und JOGL nicht von Swing oder AWT als GUI Gebrauch, sondern bietet selbst eine Lösung zur Erstellung des Programmfensters. Dies geschieht mittels der Klasse Display, die über statische Methoden ein Fenster mit entsprechend gewählten Einstellungen öffnet. Auch die Rendering-Schleife muss selbst erzeugt werden, d. h. es werden keine Methoden wie display() oder init() vorgegeben. Um auf den OpenGL Befehlssatz zugreifen zu können, bietet LWJGL wiederum Klassen mit statischen Funktionen. Dabei gibt es für jede OpenGL Version eine eigene Klasse, die den jeweiligen Befehlsumfang enthält. So können OpenGL 1.1 Befehle durch Aufrufe der Klasse GL11 abgesetzt werden. Die neueste Version, OpenGL 2.0, wird durch die Klasse GL20 repräsentiert. Auch OpenGL Erweiterungen (siehe Abschnitt 2.2.1) sind als eigene Klassen verfügbar. Die einzelnen Erweiterungen tragen den jeweiligen Typ (ARB, EXT, NV oder ATI) im Namen und können so leicht erkannt werden. 3.1.3 Middleware-Anbindungen und Szenengraphen-APIs Neben Java 3D und direkten Java-Anbindungen gibt es noch eine Reihe anderer Software-Produkte, die nicht unerwähnt bleiben sollen. Sie fallen in die Kategorie der schon in Abschnitt 2.2.3 erwähnten Middleware. Oft wird auch von so genannten Szenengraphen-APIs gesprochen. Dies verdeutlicht, dass der Fokus auf der Erstellung eines Szenengraphen liegt, während das Rendering an eine darunterliegende Schicht (z. B. eine OpenGL Anbindung für Java) weitergegeben wird. Xith3D Xith3D 9 ist ein Szenengraphen-API dessen Struktur sehr der von Java 3D ähnelt. So erfolgt die Darstellung ebenfalls in einem Canvas3D und der Szenengraph besteht aus den Klassen Node, Group und Leaf. Auch die Behandlung von Ereignissen erfolgt mittels Behavior. Diese Ähnlichkeiten machen eine Portierung von Applikationen zwischen den beiden Systemen in der Regel einfach. Unterschiede zeigen sich bei der Art des Renderings. Xith3D nutzt hierbei die zuvor erwähnten OpenGL Anbindungen JOGL (bzw. JSR 231) und LWJGL. Anbindungen an Direct3D sind nicht vorhanden. Davison kritisiert in [2] weiters, dass der Szenengraph nicht threadsicher sei, d. h. bei einer Ausführung mit mehreren parallelen Threads könnten Probleme auftreten. 9 http://www.xith.org/ KAPITEL 3. ANSÄTZE IN JAVA 30 jME jME (jMonkeyEngine)10 ist eine reine Java Game-Engine. Sie bietet Klassen für das einfache Erstellen einer simplen Rendering-Schleife sowie die Organisation der Szene in einem Szenengraphen. Das Rendering erfolgt mittels LWJGL, auch für OpenAL und FMOD sind Klassen in jME vorhanden. Updates für die Engine erfolgen in regelmäßigen Abständen. Die aktuelle Version ist 0.10. ogre4j ogre4j 11 ist eine Java-Anbindung für die frei verfügbare OGRE 3D GrafikEngine. Das Ziel des Projekts ist es, ähnlich wie bei OpenGL-Anbindungen, die Funktionalitäten der Engine in Java eins zu eins verfügbar zu machen. Die Anbindung erfolgt wiederum mittels JNI. Das Projekt hält zum gegenwärtigen Zeitpunkt bei Version 0.3. Diese ermöglicht noch nicht die komplette Nutzung aller OGRE-Funktionen in Java. Die Gründe dafür liegen laut den Entwicklern vor allem bei Problemen der Umsetzung von C++ spezifischen Konstrukten wie etwa Templates. 3.2 Existierende 3D-Java-Spiele Verglichen zu der Anzahl an kommerziellen C++ Spielen ist die Zahl an bekannten und erfolgreichen Java-Spielen sehr gering. Der Großteil aller in Java realisierten Spiele erscheint entweder auf Mobilen Plattformen oder als Java-Applets (meist Browser-basiert). Es gibt jedoch einige Spiele, die durch ihren Umfang und auch ihr Vertriebsmodell als klassische kommerzielle Spiele anzusehen und auch erfolgreich sind bzw. waren. Im Anschluss folgt eine Auflistung und kurze Beschreibung einiger dieser Spiele. Erstere enthält nicht nur rein in Java entwickelte Spiele, sondern auch solche, bei denen Java in einem großen Teilbereich (etwa für die Logik) verwendet wurde. Puzzle Pirates: Entwickelt von Three Rings Design hat sich Puzzle Pirates zu einem der bekanntesten Java-Spiele entwickelt. Es handelt sich dabei um ein Massen-Mehrspieler-Online-Rollenspiel (englisch Massive Multiplayer Online Role Playing Game, auch MMORPG abgekürzt), welches in einem typischen Piraten-Szenario angesiedelt ist. Der Spieler verkörpert dabei einen Piraten, der auf Schiffen anheuern und Schwertkämpfe austragen kann. Diese Tätigkeiten werden jedoch nicht im klassischen Sinne sondern in Form von Puzzles gelöst. Sowohl die Server-Software als auch die Client-Applikationen sind in Java geschrieben. http://www.puzzlepirates.com/ 10 11 http://jmonkeyengine.com/ http://www.ogre4j.org/ KAPITEL 3. ANSÄTZE IN JAVA 31 Façade: Das von Procedural Arts entwickelte Spiel ist ein interaktives Drama. Der Spieler erstellt einen Charakter und erhält am Beginn eine Einladung von einem befreundeten Paar. Er betritt deren Wohnung und kann ab sofort völlig ungebunden mit ihnen kommunizieren. Der Spieler tippt Fragen und Sätze per Tastatur ein und seine virtuellen Gegenüber reagieren entsprechend darauf. Verlauf und Ausgang sind somit bei jedem Spiel unterschiedlich. Façade wurde komplett mit Java und OpenGL entwickelt. Für die dahinter liegende künstliche Intelligenz wurde eine eigene Sprache names ABL (A Behavior Language) entwickelt. http://www.interactivestory.net/ Chrome: Das Spiel der Firma Techland ist ein First Person Shooter, der sich vor allem durch seine weiten, begehbaren Außenareale auszeichnet. Der Spieler kann zwischen verschiedensten Waffen wählen, weiters steht ihm auch eine Vielzahl an unterschiedlichen Fahrzeugen zur Verfügung. Chrome ist völlig in Java realisiert und erhielt im Jahr 2004 den Duke’s Choice Award“, eine Auszeichnung der Firma Sun ” Microsystems für das innovativste, auf Java-Technologie basierende Produkt. http://www.chromethegame.com/ Law and Order: Die mittlerweile drei Spiele zur bekannten TV-Serie sind klassische Detektiv-Adventures, in denen der Spieler verschiedene Mordfälle lösen muss. Die ersten beiden Teile sind komplett mittels Java realisiert, zur 3D-Darstellung werden Java 3D und QuickTime für Java verwendet. http://www.lawandordergame.com/ Tribal Trouble: Das Wikinger-Aufbauspiel der Firma Oddlabs ver- setzt den Spieler in eine bunte 3D-Welt, in der er seinen WikingerStamm im Stil von Die Siedler zum Aufstieg durch Expansion verhelfen muss. Das Spiel setzt stark auf nicht völlig ernste Elemente, so gibt es etwa nicht ganz alltägliche Waffen wie einen lebenden Hühnerpfeil. Das Spiel wurde zur Gänze in Java realisiert und steht für Windows, Linux und Mac-Plattformen zur Verfügung. http://www.tribaltrouble.com/ Wurm Online: Dabei handelt es sich um ein Fantasy MMORPG, das sich zum Ziel gesetzt hat, dem Spieler möglichst viele Freiheiten zu geben. Das Spiel wurde mit Java und OpenGL realisiert. Es wird als Java-WebStart-Datei zum Download angeboten. Somit ist nur eine Datei zur Ausführung notwendig. http://www.wurmonline.com/ Lux: Diese Strategiespiel-Reihe der Firma Sillysoft erlaubt es, große Schlachten im Stil des Brettspieles Risiko zu spielen. Die Spiele sind im mittleren Preissegment angesiedelt und komplett mit Java-Technologie realisiert. http://sillysoft.net/ Star Wars Galaxies: Bei diesem MMORPG Großprojekt von Lu- casArts wurde die Spiele-Logik komplett in der Programmiersprache Java realisiert. http://starwarsgalaxies.station.sony.com/ KAPITEL 3. ANSÄTZE IN JAVA 32 IL-2 Sturmovik: Dieses bekannte Kampfflug-Simulationsspiel von Ubisoft verwendet Java für die Spiele-Logik sowie für physikalische Berechnungen und die Umsetzung der künstlichen Intelligenz. http: //www.il2sturmovik.com/ Weitere zum Teil auch ältere Java-Spiele listet Davison in [2] auf. Er geht dabei auch auf Freeware- und Shareware-Spiele ein, die hier bewusst weggelassen wurden. Kapitel 4 JSceneViewer3D – ein Quake III Level Renderer JSceneViewer3D ist eine Applikation zur Darstellung von komplexeren 3DSzenerien unter der Verwendung von Java-Komponenten. Die vorliegende Version ermöglicht die Darstellung beliebiger Levels (Welten) des 3D-Computerspiels Quake III Arena (im weiteren Verlauf als Quake III bezeichnet). Durch die Einbindung anderer Level-Loader (Erläuterung und Details siehe Abschnitt 4.3) ist jedoch auch die Darstellung anderer 3D-Szenen möglich. Die Applikation wurde mit dem Ziel entwickelt, die Umsetzung eines performancekritischen Teils eines Spiels – in diesem Fall die grafische Darstellung – komplett unter Verwendung der Programmiersprache Java zu vollziehen. Die fertige Applikation soll im Stande sein, einen Level darzustellen und Daten über die Effektivität der Darstellung während dieser zu sammeln. Jene Daten werden in Kapitel 5 mit Werten aus dem Spiel Quake III verglichen. Neben dem Erhalt statistischer Informationen war es auch ein Ziel, Erfahrungen während des Entwicklungsprozesses zu sammeln und diese strukturiert wiederzugeben. Dieses Kapitel beschreibt den Implementierungsvorgang der Applikation. Abschließende Anmerkungen und ein Resümee finden sich in Kapitel 6. 4.1 Architektur Bei der Konzipierung der Architektur von JSceneViewer3D wurde darauf geachtet, eine möglichst modularisierte Struktur zu entwerfen. Die einzelnen Komponenten sollen weitestgehend ohne gegenseitige Abhängigkeiten funktionieren, um den Einbau von Erweiterungen oder zusätzlichen Funktionalitäten so einfach wie möglich zu gestalten. Der Aufbau von JSceneViewer3D kann in vier große Teile untergliedert werden. 33 KAPITEL 4. JSCENEVIEWER3D 34 Abbildung 4.1: Die vier Komponenten von JSceneViewer3D und ihr Zusammenspiel. Die Basis-Applikation mit der grafischen Benutzeroberfläche Der Level-Loader Der Renderer Der Logger Diese Teile befinden sich in einer sequentiellen Abarbeitungsreihenfolge, die durch den Programmfluss vorgegeben wird. Am Beginn steht dabei die Basis-Applikation, welche den Einstiegspunkt darstellt. Mit Hilfe der grafischen Benutzeroberfläche wird zunächst eine zu öffnende Level-Datei ausgewählt. Dadurch wird der zu der Datei passende Level-Loader initialisiert, welcher nun die Daten aus der Datei ausliest und in für den Renderer verständliche Datenformate speichert. Im letzten Schritt werden die Daten danach auf dem Ausgabemedium (in der Regel dem Bildschirm) dargestellt. Einzig und allein die Logger-Komponente unterliegt nicht der sequentiellen Ausführungsreihenfolge. In jedem Stadium werden wichtige Informationen von ihr gespeichert und abschließend zusammengefasst dargestellt. Die Abhängigkeiten und das Zusammenspiel dieser Komponenten sind in Abbildung 4.1 dargestellt. Auch die Paket- und Klassenstruktur der Java-Applikation spiegelt das soeben beschriebene Schema wider: KAPITEL 4. JSCENEVIEWER3D 35 net.codingmonkey.jsceneviewer3d.gui enthält jene Klassen, die zur Anzeige der grafischen Benutzeroberfläche benötigt werden. Die in diesem Paket befindliche Hauptklasse JSceneViewer3D beinhaltet ebenfalls die main() Methode, welche als Einstiegspunkt in das JavaProgramm dient. net.codingmonkey.jsceneviewer3d.gui.images beinhaltet sämtli- che Grafiken und Bilddaten, die in der grafischen Benutzeroberfläche verwendet werden. net.codingmonkey.jsceneviewer3d.io enthält Klassen, die für das Lesen und Schreiben von Daten verwendet werden. Sie werden für das Laden des Levels benötigt. net.codingmonkey.jsceneviewer3d.io.q3 enthält jene Datenstrukturen, die den Inhalt einer Quake III Level Datei widerspiegeln. net.codingmonkey.jsceneviewer3d.io.q3.entites beinhaltet Klassen, die so genannte Quake III Entitäten repräsentieren. Diese Entitäten verwalten etwa die Positionen der Lichter in der Szene. net.codingmonkey.jsceneviewer3d.renderer.j3d enthält sämtli- che Klassen, die benötigt werden, um die vom Level-Loader konstruierten Daten mittels Java 3D darzustellen. net.codingmonkey.jsceneviewer3d.logging besteht aus Klassen, die für die Aufzeichnung von Performance-Daten benötigt werden. net.codingmonkey.jsceneviewer3d.misc beinhaltet diverse Hilfsklassen, die zu unterschiedlichen Zeitpunkten im Programm benötigt werden. 4.2 Basis-Applikation Um die Darstellung von komplexen 3D-Szenen mit Java zu realisieren, ist eine grafische Benutzeroberfläche (Graphical User Interface oder kurz GUI) nicht unbedingt notwendig. Viele der existierenden und im Vorfeld dieses Projekts getesteten Loader für Quake II oder auch Quake III verzichten zumeist komplett auf ein GUI. Die Applikation wird per Kommandozeile gestartet und die Level-Dateien werden dabei als Argumente übergeben. Ein anderer Ansatz, der ebenfalls verwendet wird ist, das Menü in die 3DDarstellung zu integrieren, d. h. es mittels OpenGL darzustellen. Beispiele hierfür sind die Quake-Loader von New Dawn Software1 oder Chapter 232 . 1 2 http://www.newdawnsoftware.com/ http://www11.brinkster.com/chapter23/ KAPITEL 4. JSCENEVIEWER3D 36 public void runApplication() { SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGui(); } }); } Abbildung 4.2: Der Start der Programmschleife im Event Dispatching Thread. Bei der Umsetzung von JSceneViewer3D wurde bewusst die Realisierung einer einfachen und leicht zu handhabenden Benutzeroberfläche angestrebt, um die Verwendung des Programms so intuitiv wie möglich zu gestalten. Zur Realisierung wurde ausschließlich das in der Java Standardbibliothek enthaltene GUI API Swing (enthalten im Paket javax.swing) verwendet. Wie in Abschnitt 4.1 beschrieben, sind alle für das GUI benötigten Ressourcen in den zwei Paketen net.codingmonkey.jsceneviewer3d.gui und net.codingmonkey.jsceneviewer3d.gui.images enthalten. Die wichtigste Klasse ist JSceneViewer3D. Sie enthält die main() Methode, welche als Einstiegspunkt für das Java Programm dient. In ihr wird zuerst ein Objekt der Klasse JSceneViewer3D angelegt. Im Anschluss wird darauf die Methode runApplication() aufgerufen. Sie setzt die Applikation in Gang und erzeugt die grafische Benutzeroberfläche mittels der Methode createAndShowGui(). Wichtig ist dabei, dass dieser Ablauf im so genannten Event Dispatching Thread passiert. Dies wird durch einen Aufruf, wie er in Abbildung 4.2 dargestellt ist, erreicht. Threads sind Prozesse, die in einem Programm quasi-parallel ablaufen können. Dies ermöglicht, dass etwa ein Teil einer Applikation nicht warten muss, bis ein anderer mit einer Operation fertig ist, sondern seine Aufgaben sofort erledigen kann. Gleichzeitig bedeutet es jedoch auch, dass dadurch so genannte Deadlocks (auch Verklemmungen genannt) auftreten können, wenn zwei Prozesse auf die gleichen Ressourcen zugreifen wollen und diese nicht freigegeben sind. Die Swing Bibliotheken unterstützen die Arbeit mit mehreren Threads, wobei der zuvor erwähnte Event Dispatching Thread für die Abarbeitung von Ereignissen (Events) wie Mausklicks oder Tastatureingaben sowie für das Zeichnen der kompletten Oberfläche zuständig ist. Werden nun Dinge an der Benutzeroberfläche verändert, etwa beim ersten Aufruf alle Elemente erstellt oder im späteren Verlauf Elemente zur Laufzeit markiert, inaktiv oder unsichtbar gesetzt, so ist durch einen Aufruf im Event Dispatching Thread garantiert, dass es zu keinen Deadlocks kommt. Die Methode createAndShowGui() erzeugt zunächst ein Fenster der KAPITEL 4. JSCENEVIEWER3D 37 Abbildung 4.3: JSceneViewer3D nach dem Start. Im nächsten Schritt muss ein Level geladen werden. Klasse javax.swing.JFrame. Dieses ist das Hauptfenster, in das in weiterer Folge alle Elemente platziert werden. In einem ersten Schritt wird das Fenster in zwei Kartenreiter (auch als Tabs bezeichnet) unterteilt. Der erste Reiter wird dabei für die Informationen der geladenen Datei verwendet (Viewer Tab), der zweite bietet Platz für den Logger (Logger Tab), der nach erfolgtem Rendering statistische Daten darstellt. Im Anschluss folgt noch die Erstellung des Hauptmenüs, welches zur effektiveren Verwendung mit Tastenkürzeln für die wichtigsten Operationen ausgestattet wurde. Schließlich wird das Fenster mit der Methode setVisible(true) dargestellt. Die Applikation ist gestartet und wird wie in Abbildung 4.3 dargestellt. Der Benutzer kann nun über den Menü-Dialog File → Open eine LevelDatei öffnen. Um dies intuitiv zu gestalten, wird ihm eine erweiterte Dialogbox des Typs javax.swing.JFileChooser zur Verfügung gestellt. Mit ihrer Hilfe kann der Benutzer eine beliebige Datei auswählen. Um nur das Öffnen eines bestimmten Dateityps zu erlauben – in diesem Fall Quake III LevelDateien mit der Endung .pk3 – wird ein Dateifilter der Klasse LevelFilter verwendet. Dieser von der Klasse javax.swing.filechooser.FileFilter abgeleitete Filter blendet alle Dateien, deren Endung nicht .pk3 ist aus und verhindert so bereits auf einfache Art und Weise, dass der Benutzer eine falsche Datei zu öffnen versucht. Wurde eine Datei mit vorgegebener Endung ausgewählt, erfolgt im nächsten Schritt eine Gültigkeitsprüfung. Hierbei wird festgestellt, ob es sich tatsächlich um ein Quake III -Level, oder um ein anderes Dateiformat handelt, das lediglich die selbe Datei-Endung KAPITEL 4. JSCENEVIEWER3D 38 besitzt. Diese Aufgabe übernimmt die Klasse FileReader. Sie erhält als Parameter die mittels javax.swing.JFileChooser geöffnete Datei – repräsentiert durch die Java-Klasse java.io.File. Jene stellt eine Datei (oder auch ein Verzeichnis) auf abstrakte Art und Weise dar und erlaubt lesende und schreibende Zugriffe darauf. Über den konkreten Inhalt ist vorerst noch nichts bekannt. Dieses erste schnelle Auslesen von Dateiinformationen ist noch nicht Bestandteil des eigentlichen Level-Ladens. Vielmehr werden neben der Validität der Datei auch noch Informationen wie der Name des Levels und ein Vorschaubild ausgelesen. Diese Informationen werden im Anschluss in der Benutzeroberfläche angezeigt. Das soeben angelegte Objekt der Klasse FileReader bestimmt in einem ersten Schritt zunächst erneut die Dateierweiterung. Darauf basierend werden in weiterer Folge die Gültigkeitsprüfungen für das jeweilige Format durchgeführt. Hat eine Datei etwa die Endung .pk3, so wird anhand des Dateiinhalts überprüft, ob es sich wirklich um eine Quake III -Level-Datei handelt. An dieser Stelle bietet die Klasse Platz für mögliche Erweiterungen. Soll etwa neben dem Quake III Format auch noch das Unreal Tournament3 Format unterstützt werden, so muss hier lediglich für die Erweiterung .ut2 eine Überprüfung eingebaut werden. Im Falle von Quake III wird überprüft, ob eine Datei im Zip-Format vorliegt, da PK3 ein Zip komprimiertes Containerformat ist. Anschließend wird in dem Archiv eine Datei mit der Erweiterung .arena gesucht. Sie enthält den Namen und den Spieltyp der Karte. Schließlich wird im Verzeichnis levelshots ein JPEG- oder TGA-Bild mit der Level-Vorschau ausgelesen. Wurden alle Daten erfolgreich gesammelt, gilt die Datei als gültige Quake III -Datei. Kommt es zu Fehlern während dieses Prozesses, werden Exceptions (Ausnahmen) geworfen, welche an das GUI weitergeleitet und dort durch entsprechende Fehlermeldungen dem Benutzer mitgeteilt werden. Wurde die Datei erfolgreich geöffnet, zeigt JSceneViewer3D in der linken Spalte Dateiname, Name und Typ der Karte sowie das Vorschaubild an (Abbildung 4.4). In der rechten Spalte kann der Benutzer nun einige Optionen konfigurieren, bevor mit dem Laden des Levels und der Darstellung begonnen wird. Mittels Radio-Buttons (Auswahlliste ohne die Möglichkeit der Mehrfachauswahl) wird zunächst der Renderer selektiert. In der momentan verfügbaren Implementierung ist nur Java 3D anwählbar. JSR 231/JOGL ist jedoch als nicht anwählbare Option bereits aufgelistet, um Erweiterungsmöglichkeiten in diese Richtung zu signalisieren. Darunter befinden sich Optionen, die die Darstellung beeinflussen. Es kann gewählt werden, ob Texturen geladen und angezeigt werden, ob Lightmaps zum Einsatz kommen sollen (ebenfalls noch nicht verfügbar) oder ob der Szenengraph kompiliert werden soll. Weiters kann der Benutzer die Anzahl der Tessellierungsschritte bestimmen, die zu verwendende Auflösung einstellen und auswählen, ob die Darstellung 3 http://www.unrealtournament.com/ KAPITEL 4. JSCENEVIEWER3D 39 Abbildung 4.4: JSceneViewer3D mit geladenem Level. Das Vorschaubild wird angezeigt, rechts können Optionen eingestellt und der Rendervorgang gestartet werden. im Vollbild-Modus erfolgen soll. Nachdem der Benutzer die gewünschten Einstellungen getroffen hat, kann die Darstellung durch einen Klick auf den Knopf Start Rendering“ gestartet werden. Durch einen Klick auf Defaults“ ” ” werden die Optionen auf die voreingestellten Standardwerte zurückgesetzt. 4.3 Level-Loader Die Aufgabe eines Loaders (Laders) ist es, Daten, die in einem bestimmten Format vorliegen, zu lesen und so zu verarbeiten, dass sie von einer Applikation weiter verwendet werden können. Ein Level-Loader ist eine Spezialform, die Daten von Spiele-Leveln (Welten, Szenerien) lädt und so aufbereitet, dass diese vom Renderer des Spiels (im Falle von JSceneViewer3D ist dies Java 3D) dargestellt werden können. Die Implementierung von JSceneViewer3D sieht vor, dass für jedes zu verarbeitende Level-Format (Quake III usw.) ein eigener Loader existiert. Für die Rendering-Einheit ist es daher nicht notwendig zu wissen, welches Format sie gerade darstellt, denn es obliegt dem Loader, die Daten in ein für sie verwertbares Format zu konvertieren. Die Auswahl des passenden Level-Loaders erfolgt in JSceneViewer3D bereits mit dem Öffnen der zu ladenden Datei. Ist die Datei als gültig empfunden worden (siehe dazu Abschnitt 4.2), speichert die Applikation den gegenwärtigen Typ des Levels. Startet nun der Benutzer den Rendervorgang, wird KAPITEL 4. JSCENEVIEWER3D 40 zunächst der für den aktuellen Level-Typ zugeordnete Loader ausgewählt. Diese Auswahl erfolgt mit Hilfe der Klasse LoaderFactory. Sie besitzt die statische Methode createLevelLoader(LevelType type), welche den momentanen Typ des Levels als Parameter erhält. In dieser Methode wird dann ein Objekt des passenden Level-Loaders erzeugt und zurückgegeben. createLevelLoader(LevelType type) basiert dabei zum Teil auf dem Entwurfsmuster Fabrikmethode“ (auch als Factory Method oder nur Fac” tory bezeichnet), welches in [6] eingehend beschrieben wird. Alle LevelLoader müssen die Schnittstelle com.sun.j3d.loaders.Loader implementieren. Diese ist Teil des Java 3D API und sorgt dafür, dass ein Loader definierte Zugriffsmethoden besitzt und die geladenen Daten in einem ebenfalls definierten Format ausgibt. Für den in JSceneViewer3D implementierten Loader bedeutet dies, dass er auch in anderen Applikationen, die Quake III Levels mittels Java 3D darstellen wollen, problemlos verwendet werden kann. Die Fabriksmethode nützt dabei das Konzept der gemeinsamen Schnittstelle aus und definiert, dass der Rückgabewert ein Objekt sein wird, welches diese implementiert. Der konkrete Objekttyp ist nicht bekannt und wird erst zur Laufzeit bestimmt. Der aktuelle Level-Typ wird herangezogen und der entsprechende Loader wird erzeugt und zurückgegeben. Im Falle eines Quake III Levels wird ein Exemplar von Quake3LevelLoaderJ3D angelegt. Entsprechend der implementierten Schnittstelle bietet der Loader drei Methoden, um Daten zu laden. Diese sind: load(String file), load(URL url) und load(Reader reader). Der Rückgabewert ist in allen drei Fällen das Interface com.sun.j3d.loaders.Scene, welches definierten Zugriff auf einen, von einem Loader erstellten Szenengraphen bietet. In der momentan vorliegenden Version des Loaders sind jedoch nur die Methoden mit java.lang.String und java.net.URL als Parametertypen implementiert. Dies ist auf Probleme beim Auslesen der Daten aus einem java.io.Reader Objekt zurückzuführen. Jenes bietet Zugriff auf die in ihm gekapselten Zeichen-Ströme (Character-Streams), also in Form von frei darstellbarem Text. Für den Loader werden aber nicht konvertierte Binärdaten benötigt. Generell sind die Datentypen, die als Parameter für die load() Methode benötigt werden, schlecht gewählt. Zumeist dürften von einem Level-Loader Daten aus einer Datei geladen werden. Der Datentyp java.io.File wäre hierbei als Parameter sicher besser geeignet als etwa ein java.io.Reader. Auch die Möglichkeit, den Pfad zu einer Datei mittels java.lang.String und java.net.URL angeben zu können, erweist sich als nicht unbedingt notwendig, zumal sich Pfade aus beiden Datentypen mit Methoden der Standardbibliothek sehr leicht in den jeweils anderen konvertieren lassen. Mit dem Aufruf einer load() Methode beginnt das eigentliche Laden des Levels. Abbildung 4.5 stellt die Methode dar. Die Level-Datei wird nun zunächst in ein Objekt der Klasse PK3File geladen. Sie besitzt eine Datenkomponente (Exemplarvariable) des Typs java.util.zip.ZipFile, welche die Quake III Level-Datei intern repräsentiert. Dies ist möglich, da das PK3 KAPITEL 4. JSCENEVIEWER3D 41 public Scene load(String file) throws FileNotFoundException, IncorrectFormatException, ParsingErrorException { Scene quakeScene = null; try { PK3File pakFile = new PK3File(file); ZipEntry bspfile = pakFile.getBSPFile(); InputStream bspStream = pakFile.getInputStream(bspfile); byte[] data = ByteArrayBuilder.fromStream(bspStream); bspStream.close(); quakeScene = processData(data, pakFile); } catch (IOException e) { e.printStackTrace(); } return quakeScene; } Abbildung 4.5: Die Methode load() des Quake III Level-Loaders. Format wie bereits zuvor erwähnt ein Zip-Container ist. Neben der Repräsentation als Zip-Datei bietet die Klasse PK3File Zugriffsmethoden auf einzelne Dateien im Container und deren Inhalte. Diese Inhalte werden von Java als Datenstrom (Stream) dargestellt. Mit Hilfe jener Datenströme können die Inhalte der Datei gelesen und auch verändert werden. Details zu Datenströmen in Java können [23] entnommen werden. Durch den Aufruf der Methode getBSPFile() wird aus der PK3-Datei die eigentliche Level-Datei extrahiert. Sie hat die Erweiterung .bsp und enthält alle Geometriedaten, Texturkoordinaten, Informationen über verwendete Lightmaps usw., die für eine korrekte Darstellung benötigt werden. Im Folgenden wird für diese Datei der Name BSP-Datei verwendet (das Kürzel BSP bezeichnet den in dieser Arbeit in Abschnitt 2.3.3 beschriebenen BSP-Baum, der in Quake III beim Rendering zum Einsatz kommt). Um den Inhalt der BSP-Datei auslesen zu können, wird mittels der Methode getInputStream() auf den zugehörigen Datenstrom zugegriffen. Die Daten im Quake III Format sind sequentiell angeordnet, wobei die Startpunkte der einzelnen Datenpakete durch Offsets ab Dateianfang festgelegt sind (siehe dazu auch Anhang A). Diese Offsets sind in Byte spezifiziert, daher bietet es sich an, den Datenstrom in ein Byte-Array (Datenfeld bestehend aus Bytes) zu konvertieren. Genau das erledigt die Methode fromStream() der KAPITEL 4. JSCENEVIEWER3D 42 Klasse ByteArrayBuilder. Nach ihrem Aufruf stehen alle Level-Daten in einem Byte-Array zur Verfügung, welches nun in weiterer Folge ausgelesen und verarbeitet werden kann. 4.3.1 Auslesen der Lump-Daten Die Verarbeitung und Aufbereitung der gesamten Level-Daten geschieht in der Methode processData(byte[] data, PK3File pakFile) (Abbildung 4.6). Der zweite Parameter neben dem Byte-Array ist die PK3-Datei. Diese wird nochmals benötigt, da in ihr alle Texturen, die in der Welt verwendet werden, enthalten sind. Aus dem Array werden nun die Daten, die logisch zu so genannten Lumps zusammengefasst sind, ausgelesen. Der Begriff Lump (zu Deutsch Klumpen) bezeichnet ein Stück Information innerhalb der BSP-Datei. Das Quake III Format enthält insgesamt 17 solcher Lumps, die hintereinander angeordnet sind. Ein so genannter Header am Beginn der Datei enthält Informationen über die Position und die Länge der einzelnen Pakete. Anhang A enthält eine detaillierte Beschreibung aller Lumps und deren Bedeutung. Die Informationen stammen zum größten Teil aus [17]. Um das Byte-Array, das zuvor angelegt wurde, zu verarbeiten, wurde die Struktur des Quake III Level-Formats mittels Java-Objekten nachempfunden. So werden der Header und alle benötigten Lumps durch eigene Objekte repräsentiert, die die enthaltenen Informationen kapseln. Header-Informationen Zunächst wird das Header-Objekt, welches Informationen über den Aufbau der Datei enthält, angelegt. Es wird durch die Klasse BSPHeader repräsentiert. Im Konstruktor wird zu Beginn das Byte-Array wieder in einen Datenstrom verwandelt. Hierbei muss die Anordnung der Bytes beachtet werden. Die Programmiersprache Java verwendet das so genannte Big-Endian System, in dem das höchstwertige Byte an der niedrigsten Stelle im Speicherbereich geschrieben wird. Die Daten des Quake III Levels liegen jedoch im Little-Endian System (höchstwertiges Byte an der höchsten Stelle im Speicherbereich) vor. Somit würde bei einer direkten Verwendung der ByteDaten die Reihenfolge nicht mehr stimmen. Daher wird bei der Umwandlung in einen Datenstrom die Klasse LittleEndianDataInputStream verwendet, welche wie die in der Java-Standardbibliothek enthaltene Klasse java.io.InputStream Methoden zum sequentiellen Auslesen von bestimmten Datentypen (readInt(), readFloat(), usw.) bereitstellt, jedoch eine korrekte Verschiebung der Bytes vornimmt. Die ersten beiden Werte im Header spezifizieren den Typ der Datei und die verwendete Version. Sie werden allerdings nicht weiter benötigt. In weiterer Folge werden die Offsets und Längen aller 17 Lumps ausgelesen und in einem Array gespeichert. Die KAPITEL 4. JSCENEVIEWER3D 43 private Scene processData(byte[] data, PK3File pakFile throws IOException { SceneBase scene = new SceneBase(); BSPHeader header = new BSPHeader(new ByteArrayInputStream(data)); BSPEntityLump entities = new BSPEntityLump(header, new ByteArrayInputStream(data)); BSPVertexLump vertices = new BSPVertexLump(header, new ByteArrayInputStream(data)); BSPMeshvertLump meshverts = new BSPMeshvertLump(header, new ByteArrayInputStream(data)); BSPTextureLump textures = new BSPTextureLump(header, new ByteArrayInputStream(data), pakFile); BSPLightmapLump lightmaps = new BSPLightmapLump(header, new ByteArrayInputStream(data)); BSPFaceLump faces = new BSPFaceLump(header, new ByteArrayInputStream(data)); BranchGroup branchGroup = new BranchGroup(); faces.render(vertices, meshverts, textures, branchGroup); entities.process(branchGroup); scene.setSceneGroup(branchGroup); return scene; } Abbildung 4.6: Die Methode processData(). Für jeden Lump wird ein Objekt angelegt, anschließend wird der Szenengraph erzeugt. Anordnung der Lumps ist vom Quake III -Format vorgegeben. Zugriffsmethoden erlauben im weiteren Verlauf das Auslesen dieser Daten. Entitäten Der erste ausgelesene Lump ist der Entitäten-Lump. Er enthält keine Informationen zur Erzeugung des Levels sondern beinhaltet die Position von so genannten Entitäten in der Szene. Diese bestimmen etwa die Startposition des Spielers in der Welt, Positionen für Bonusgegenstände oder auch die Koordinaten der Lichtquellen. In JSceneViewer3D werden drei Entitäten KAPITEL 4. JSCENEVIEWER3D 44 ausgelesen und gespeichert: worldpawn: Enthält allgemeine Informationen zum aktuellen Level. Das Attribut ambient beinhaltet die Farbe der ambienten Beleuchtung (Umgebungsbeleuchtung), im Attribut message kann ein beliebiger Text (meist eine Nachricht des Level-Designers) stehen. light: Diese Entität repräsentiert eine Lichtquelle. Verschiedene Attri- bute beschreiben die Lichtquelle näher. origin enthält die Position, _color die Farbe der Lichtquelle. Das Attribut light bestimmt die Intensität. Wird die Entität nur mit diesen drei Attributen spezifiziert, handelt es sich um eine Punktlichtquelle. Werden zusätzlich noch target und radius angegeben, so wird das Licht als kegelförmiges SpotLicht kreiert, welches auf einen bestimmten Punkt ausgerichtet ist. infoplayerdeathmatch: Dahinter verbirgt sich die Startposition des Spielers in einer Quake III Welt. Das Attribut origin enthält die Koordinaten, angle die Rotation des Spielers. Der Eintrag arena enthält die Nummer der Arena, in der der Spieler startet, falls ein Level mehrere Arenen (große Regionen) enthält. Beim Anlegen des Objekts für den Entitäten-Lump wird zuerst erneut LittleEndianDataInputStream aus dem Byte-Array erzeugt. Danach wird im Datenstrom mittels des im Header angegebenen Offsets zu einer bestimmten Stelle gesprungen. Von dort aus wird entsprechend der Länge, die im Header für diesen Lump gespeichert ist, eine bestimmte Anzahl von Bytes ausgelesen. Die Bytes werden im Anschluss in eine Zeichenkette (java.lang.String) konvertiert und der Klasse EntityParser übergeben. Jene Klasse arbeitet die Zeichenkette ab und extrahiert die zuvor erläuterten Entitäten. Aus jeder Entität wird dabei ein Objekt erzeugt. Die entstehenden Objekte gehören den Klassen WorldSpawnEntity, LightEntity und InfoPlayerDeathmatchEntity an. Sie sind alle von der abstrakten Basisklasse BSPEntity abgeleitet und befinden sich aus Gründen der besseren Übersicht im Paket net.codingmonkey.jsceneviewer3d.io.q3.entites. Die Klasse BSPEntityLump verwaltet alle neu kreierten Entitäten-Objekte. Diese werden im späteren Verlauf in den Szenengraphen integriert (siehe Abschnitt 4.4). Vertices Im nächsten Schritt liest der Level-Loader alle Vertices (Eckpunkte eines Polygons) der Szene ein. Diese werden in der Klasse BSPVertexLump gespeichert. Ein einzelner Vertex wird dabei wiederum durch eine eigene Klasse (BSPVertex) repräsentiert. Ein Vertex definiert sich durch seine Position, seine Texturkoordinaten für Texturen und Lightmaps, seine Normale und durch seine Farbe. Der Vorgang des Einlesens gleicht dem der anderen KAPITEL 4. JSCENEVIEWER3D 45 Lumps. Zunächst wird ein Datenstrom angelegt, aus dem dann ab dem im Header definierten Offset eine gewisse Anzahl an Bytes gelesen wird. Während des Vorgangs werden pro Vertex zunächst zehn float Gleitkommawerte ausgelesen (drei Werte für die x, y und z Koordinaten der Position, zwei mal zwei Werte für die u und v Texturkoordinaten und wiederum drei Werte für die Normale). Anschließend werden noch vier Bytes für die Farbinformation (rot, grün, blau und alpha) gelesen. Der Vorgang wird für die gesamte Anzahl an Vertices wiederholt. Diese lässt sich zuvor errechnen, in dem die Länge des Vertex-Lumps in Bytes durch die Größe eines Verticis in Bytes dividiert wird. Mesh-Vertices Der nächste vom Loader verarbeitete Lump ist der MeshVert-Lump. Das Quake III Format beschreibt MeshVerts als Offsets zum ersten Vertex in einem Polygon. Anders ausgedrückt handelt es sich dabei um Vertex-Indices, die nicht absolut auf ein komplettes Vertex-Array, sondern lediglich relativ auf die Vertices des aktuellen Polygons anwendbar sind. Das Auslesen dieser Indices erfolgt analog zu den anderen Lumps. Die Daten werden in der Klasse BSPMeshVertLump gespeichert. Texturen Die Verwendung von Texturen im Quake III Format ist genau definiert. Zum Einsatz kommen Grafiken in den Formaten JPEG und TGA (Targa). Die Seitenlängen jeder Textur müssen zudem einer Potenz zur Basis 2 (2, 4, 8, 16, 32, 64, 128, 256, usw.) entsprechen. Um Texturen laden und anzeigen zu können, müssen deren Namen bekannt sein. Der Texturen-Lump enthält genau diese Informationen. Er definiert eine Textur über deren Namen, einen Parameter (so genannte Flags) und einen Zahlenwert, der für die Beschreibung des Inhalts verwendet werden kann (Typennummer). Diese Informationen werden in ein Array aus so genannten TextureInfo Objekten gelesen. Die Anzahl wird dabei durch die Division der Länge des Lumps mit der Größe eines TextureInfo-Objekts bestimmt. Nachdem die Namen aller Texturen ausgelesen wurden, wird die Methode createTextures(PK3File pakFile) aufgerufen. Sie dient dazu, auf Basis der soeben gesammelten Informationen, Java-Objekte von den Texturen anzulegen, die der Renderer dann zur Darstellung verwenden kann. Die Java-Standardbibliothek stellt seit Version 1.4 mit dem Paket javax.imageio eine komfortable Möglichkeit zur Verfügung, Bilder mit Hilfe eines einzigen Methodenaufrufs zu laden. Leider ist die Auswahl der verwendbaren Formate begrenzt, und so wird zwar das JPEG Format unterstützt, TGA jedoch nicht. Dieser Umstand macht die Verwendung des Java Image Management Interfaces (JIMI ) notwendig. JIMI ist eine mittlerweile als veraltet angesehene Bibliothek zum Laden KAPITEL 4. JSCENEVIEWER3D 46 von Bildern unter Java. Ihre Nachfolger sind die bereits erwähnten ImageIO Klassen und das Java Advanced Imaging (JAI ) API. Diese bieten jedoch allesamt keine Unterstützung für das TGA Format, weshalb schließlich doch auf JIMI zurückgegriffen werden musste. Nachdem aus der entsprechenden TextureInfo Klasse der Name der Textur extrahiert wurde, wird versucht, die entsprechende Textur entweder mit .jpg oder .tga Dateierweiterung aus der PK3 Datei zu entpacken. Glückt dies, wird auf den darunter liegenden Datenstrom zugegriffen und mit ihm unter Verwendung des mit Java 3D mitgelieferten Textur-Laders (Klasse com.sun.j3d.utils.image.TextureLoader) ein Objekt vom Typ javax.media.j3d.Texture erzeugt. Alle so erstellten Texturen werden im BSPTextureLump Objekt gespeichert und zu einem späteren Zeitpunkt verwendet. Lightmaps Lightmaps sind rein aus Farbwerten bestehende Texturen, die von Quake III verwendet werden, um das Level beleuchten zu können, ohne rechenintensive Lichtquellen verwenden zu müssen. In der BSP-Datei werden diese im Lightmap-Lump spezifiziert. Die Texturen sind 128 × 128 Pixel groß und durch RGB (rot, grün, blau) Werte definiert. Die Farbwerte werden in einzelne Lightmap Objekte verpackt und von der Klasse BSPLightmapLump verwaltet. Die Lightmaps kommen während des Rendervorgangs zum Einsatz. Faces Als Faces bezeichnet die Quake III Spezifikation Polygone, also Flächen, die aus mehreren Vertices bestehen. Im Regelfall sind dies Dreiecke, aus denen in weiterer Folge beliebige Flächen erzeugt werden. Die Informationen über Faces in einem Quake III Level sind im FaceLump enthalten. JSceneViewer3D verwaltet sie in der Klasse BSPFaceLump. Der Face-Lump enthält eine bestimmte Anzahl von Faces (repräsentiert durch die private innere Klasse Face), die sich durch die Division der Länge des Lumps mit der Größe eines Faces errechnen lässt. Ein einzelnes Face enthält eine Reihe von Attributen, die genau spezifizieren, wie es aufgebaut ist. Die Attribute sind: texture: Index auf die Textur, die auf das Face angewandt werden soll. Index referenziert eine Textur aus der Klasse BSPTextureLump. effects: Index auf den Effekt-Lump, falls für dieses Face ein Effekt (wie etwa Nebel) verwendet werden soll. Andernfalls ist der Index 1. Das Attribut ist in der gegenwärtigen Implementierung nicht von Bedeutung, da zur Zeit keine Effekte eingesetzt werden. KAPITEL 4. JSCENEVIEWER3D 47 type: Spezifiziert, ob es sich bei dem Face um ein Mesh, ein Polygon, einen Bézier-Patch oder ein Billboard handelt. Auf diese Subtypen wird in Abschnitt 4.4 genauer eingegangen. vertex: Index des ersten Verticis im Face. n vertexes: Anzahl der Vertices im Face. meshvert: Index des ersten Mesh-Verticis im Face. n mehsverts: Anzahl der Mesh-Vertices (Indices) im Face. lm index: Index der Lightmap. Er referenziert auf die Klasse BSPLightmapLump. lm start: Spezifiziert die linke obere Ecke der Lightmap für dieses Face in der gesamten Lightmap-Textur. lm size: Beziffert die Ausdehnung der Lightmap-Textur für dieses Face innerhalb der gesamten Lightmap. lm origin: Weltkoordinaten der Lightmap. lm vecs: Textur-Vektoren für die Lightmap. normal: Oberflächen-Normale für dieses Face. size: Größe eines Bézier-Patches (falls es sich bei dem Face um einen solchen handelt). Alle Attribute werden nun aus der BSP-Datei ausgelesen und in der Klasse BSPFaceLump gespeichert. Nach dem Abschluss dieses Vorgangs ist das eigentliche Laden des Levels abgeschlossen. Alle in der vorliegenden Version notwendigen Daten wurden aus der BSP-Datei gelesen und können nun für die Darstellung verarbeitet werden. 4.4 Rendering Um eine virtuelle Welt mit Java 3D darzustellen, bedarf es streng genommen gar nicht des Entwurfs eines eigenen Renderers. Java 3D übernimmt diese Aufgabe selbst und verlangt lediglich einen zuvor korrekt konstruierten Szenengraphen (wie in Abschnitt 3.1.1 beschrieben). Da also der eigentliche Rendervorgang völlig von Java 3D übernommen wird, soll an dieser Stelle der Aufbau des Szenengraphen beschrieben werden, auch wenn der Vorgang ebenso zum Level-Loading gezählt werden könnte. In der Struktur von JSceneViewer3D wird die Integration der Daten in den Szenengraphen auch vom Quake III Level-Loader vorgenommen. Dies geschieht in der Klasse BSPFaceLump. Sie ist die wichtigste aller Lump-Klassen, da sie die Informationen über die Zusammensetzung der Geometrie enthält und auf die anderen Lump-Klassen zugreifen muss. KAPITEL 4. JSCENEVIEWER3D 4.4.1 48 Erstellung des Szenengraphen Zuständig für die Eingliederung der Daten in den Java 3D Szenengraph ist die Methode render() der Klasse BSPFaceLump. In dieser Methode wird über alle zuvor angelegten Objekte der inneren Klasse Face (also über alle zu erstellenden Faces) iteriert und wiederum eine Methode render(), diesmal jedoch die jedes einzelnen Faces, aufgerufen. Wie in Abbildung 4.7 dargestellt, wird zunächst für jedes Face ein Objekt der Klasse javax.media.j3d.Appearance, welches für das Erscheinungsbild von Geometrie zuständig ist, erzeugt. In diesem Objekt können alle so genannten Rendering States (Zustände des Objekts während des Renderings) manipuliert werden. So werden zunächst Attribute für die Darstellung eines Polygons mittels der Klasse javax.media.j3d.PolygonAttributes gesetzt. Dem Renderer wird etwa mitgeteilt, dass auch vom Betrachter abweisenden Polygone gerendert (dargestellt) werden sollen (Aufruf der Methode setCullFace(PolygonAttributes.CULL_NONE)), dass jedes erzeugte Polygon auch vollständig gefüllt werden soll (mittels Aufruf der Methode setPolygonMode(PolygonAttributes.POLYGON_FILL)) und dass die Normalen der vom Betrachter abweisenden Flächen gedreht werden sollen (per setBackFaceNormalFlip(true)). Danach wird mit Hilfe des Textur-Indicis die korrekte Textur geladen und ebenfalls dem Erscheinungsbild hinzugefügt. Durch die Erzeugung und Anwendung eines Objekts der Java 3D-Klasse javax.media.j3d.TextureAttributes können Attribute für die soeben erzeugte Textur, z. B. die Art wie die Farbe der Textur mit einstrahlendem Licht (bzw. dessen Farbe) reagiert, gesetzt werden. Sind Texturen deaktiviert, wird ein Material der Klasse javax.media.j3d.Material erzeugt, welches in weiterer Folge die Vertex-Farben aufnehmen wird. Im Anschluss an die Erstellung des Erscheinungsbildes folgt die Erzeugung der Level-Geometrie. Dabei ist zunächst das Face Attribut type, welches bereits in Abschnitt 4.3 erwähnt wurde, von großer Bedeutung. Das Quake III -Level-Format unterstützt vier verschiedene Typen von Faces: Meshes, Polygone, Bézier-Patches und Billboards. Meshes und Polygone sind jeweils Ansammlungen von Dreiecken, wobei Meshes auch nicht zusammenhängende Flächen enthalten können, während Polygone immer eine große, verbundene Fläche bilden. Der Rendervorgang ist jedoch für beide Typen identisch. Darum kann in der render() Methode, bei der Abfrage des FaceTyp Attributs auch der selbe Code für beide Varianten verwendet werden. Abbildung 4.9 stellt diesen Teil der Methode dar. Zunächst wird ein Array aus Dreiecken der Klasse javax.media.j3d.TriangleArray angelegt. Diese Datenstruktur nimmt eine Liste von Vertices auf, die intern zu Dreiecken zusammen gefasst werden. So wird das erste Dreieck durch die Vertices an den Stellen 0, 1 und 2 gebildet, das Folgende mit den Vertices an den Positionen 3, 4 und 5 usw. (Abbildung 4.8 (a)). Da somit Vertices auch bei angrenzenden Dreiecken nicht mehrfach verwendet werden, ist diese Datenstruktur in der KAPITEL 4. JSCENEVIEWER3D 49 public void render(BSPVertexLump vertices, BSPMeshvertLump meshverts, BSPTextureLump textures, BranchGroup branchGroup) { Appearance appearance = new Appearance(); PolygonAttributes attributes = new PolygonAttributes(); attributes.setCullFace(PolygonAttributes.CULL_NONE); attributes.setPolygonMode(PolygonAttributes.POLYGON_FILL); attributes.setBackFaceNormalFlip(true); appearance.setPolygonAttributes(attributes); int vertexFormat; if (RenderingOptions.isTextured()) { Texture tex = textures.getTexture(texture); appearance.setTexture(tex); TextureAttributes texAttributes = new TextureAttributes(); texAttributes.setTextureMode(TextureAttributes.MODULATE); appearance.setTextureAttributes(texAttributes); vertexFormat = GeometryArray.COORDINATES | GeometryArray.NORMALS | GeometryArray.TEXTURE_COORDINATE_2; } else { Material material = new Material(); material.setAmbientColor(new Color3f(0.0f, 0.0f, 0.0f)); material.setSpecularColor(new Color3f(0.1f, 0.1f, 0.1f)); appearance.setMaterial(material); vertexFormat = GeometryArray.COORDINATES | GeometryArray.NORMALS | GeometryArray.COLOR_4; } [...] } Abbildung 4.7: Der Beginn der Methode render() für ein Face. Hier wird die so genannte Appearance, also das Aussehen des Faces, festgelegt. Eine Rolle spielen dabei vor allem das Material und die Textur. Regel nicht so effizient wie etwa ein Triangle-Strip Array (Abbildung 4.8 (b)) oder ein Triangle-Fan Array (Abbildung 4.8 (c)), die durch das Teilen von Vertices Rechenzeit sparen können. Mit der fertigen Datenstruktur und dem zuvor angelegten Erscheinungsbild wird dann ein neues Mesh oder Polygon erzeugt. Java 3D verwendet dafür den allgemeinen Begriff 3D-Form“. Repräsentiert wird sie durch die ” Klasse javax.media.j3d.Shape3D. Diese ist gleichzeitig ein Leaf-Knoten und kann somit in die BranchGroup des Szenengraphen gehängt werden. Um gebogene oder kurvige Flächen darzustellen, bedient sich das Quake III -Format der so genannten Bézier-Patches. Sie sind die dreidimensionale KAPITEL 4. JSCENEVIEWER3D (a) 50 (b) (c) Abbildung 4.8: Verschiedene Arten, Dreiecke zu speichern. Einzelne Dreiecke als Triangle-Array (a), zusammenhängende Dreiecke als Triangle-Strip (b), zusammenhängende Dreiecke als Triangle-Fan (c). Variante von Bézier-Kurven und eine effektive Methode, gekrümmte Flächen zu speichern. Es werden nämlich nicht alle Vertices, die eine Kurve darstellen (für eine perfekte Kurve also theoretisch unendlich viele) in der LevelBeschreibung gespeichert, sondern lediglich so genannte Kontroll-Vertices, die eine ungefähre Form der gewünschten Kurve vorgeben. Anhand dieser Vertices kann dann mittels so genannter Vertex-Tessellierung die gekrümmte Fläche berechnet werden. Dabei werden neue Vertices erzeugt, die dann für die Darstellung des Patches verwendet werden. Je höher der Tessellierungsgrad ist, desto mehr Vertices werden errechnet und desto runder wirkt die Kurve. Es bleibt also der jeweiligen Anwendung überlassen, wie genau sie die Berechnungen der gekrümmten Flächen durchführen möchte. Somit kann zwischen einem rechenintensiveren Modell und einer weniger exakten Darstellung abgewogen werden. Abbildung 4.10 zeigt einen Bézier-Patch mit dazugehörigen Kontrollpunkten. In Abbildung 4.11 werden die Auswirkungen verschiedener Tessellierungsgrade dargestellt. Eine Übersicht über unterschiedliche Algorithmen und Methoden bei der Berechnung von BézierPatches gibt [1]. Die Tessellierung geschieht innerhalb der render() Methode durch den Aufruf von tessellate(int level, BSPVertexLump vertices). Jene Methode erhält als Parameter den Tessellierungsgrad und den Vertex-Lump. Letzterer wird benötigt, um auf die Kontroll-Vertices eines jeden Patches zugreifen zu können. Der verwendete Algorithmus ist eine Modifikation des KAPITEL 4. JSCENEVIEWER3D 51 public void render(BSPVertexLump vertices, BSPMeshvertLump meshverts, BSPTextureLump textures, BranchGroup branchGroup) { [...] switch (type) { case Face.POLYGON: case Face.MESH: TriangleArray array = new TriangleArray(nMeshverts, vertexFormat); for (int i = 0; i < nMeshverts; i++) { int meshvertOffset = meshverts.getOffset(meshvert + i); int index = vertex + meshvertOffset; array.setCoordinate(i, vertices.getVertex(index)); array.setNormal(i, vertices.getNormal(index)); if (RenderingOptions.isTextured()) { array.setTextureCoordinate(0, i, vertices.getTexCoord(index)); } else { array.setColor(i, vertices.getColor(index)); } } Shape3D shape = new Shape3D(array, appearance); branchGroup.addChild(shape); break; [...] } } Abbildung 4.9: Das Rendering von Polygonen und Meshes. Zum Einsatz kommt ein Array aus Dreiecken. in [16] vorgestellten Ansatzes. Die Modifikationen betreffen vor allem die Anpassung der Datenstrukturen an Java bzw. Java 3D. Der Rückgabewert der Tessellierungs-Methode ist ein Array aus neu generierten Vertices des Typs BSPVertex. Da der Algorithmus die Vertices für eine Verwendung in einem Triangle-Strip Array generiert, ist es nötig, Indices für einen korrekten Zugriff zu generieren. Dies geschieht in der Methode calculateIndices(int level). Anschließend wird das Triangle-Strip Array (in Form der Klasse javax.media.j3d.TriangleStripArray) angelegt. Zusammen mit dem zuvor erstellten Erscheinungsbild wird für jeden Bézier-Patch ein 3D-Form-Objekt kreiert und dem Szenengraphen hinzugefügt. Nachdem die Geometrie erzeugt wurde, werden nun noch die bereits ausgelesenen Entitäten verarbeitet. Um diese Aufgabe zu erledigen, wird KAPITEL 4. JSCENEVIEWER3D 52 Abbildung 4.10: Die schematische Darstellung eines Bézier-Patches. Die neun Kontroll-Vertices sind rot dargestellt. die Methode process(BranchGroup branchGroup) der Klasse BSPEntityLump aufgerufen. Diese enthält bereits alle Entitäten in einer Datenstruktur gespeichert. Jedes einzelne Element wird nun abgearbeitet und entsprechend seines Typs verarbeitet. Handelt es sich um eine Entität des Datentyps WorldspawnEntity (von der in der Regel nur eine einzige existiert), wird ein Objekt des Typs javax.media.j3d.AmbientLight angelegt und mit den Werten der Entität initialisiert. Jenes Objekt wird anschließend zu der als Parameter übergebenen BranchGroup hinzugefügt. Somit wird die Szene mit ambientem Licht ausgeleuchtet. Bei Entitäten, die eine Lichtquelle spezifizieren (LightEntity), wird zunächst überprüft, ob es sich um eine Punktlichtquelle oder um ein Spot-Licht handelt. Dementsprechend wird entweder ein Objekt der Klasse javax.media.j3d.PointLight oder javax.media.j3d.SpotLight angelegt, mit den aus der Entität ausgelesenen Werten initialisiert und in den Szenengraphen integriert. Bei Entitäten des Typs InfoPlayerDeathmatchEntity kann die Position der Kamera an die darin spezifizierten Koordinaten gesetzt werden, um den Spieler an der richtigen Position starten zu lassen. Dies wurde in der momentan vorliegenden Version noch nicht berücksichtigt. Mit dem Verarbeiten der Entitäten endet die bis zu diesem Zeitpunkt aktive Methode processData(byte[] data, PK3File pakFile) und somit auch die Konstruktion des Content-Branch-Graph des Szenengraphen. 4.4.2 Darstellung der Szene Die Anzeige der Szene beginnt damit, dass die Basis-Applikation ein neues Fenster vom Typ J3DRenderWindow öffnet. Diese Klasse ist eine Erweiterung des Typs javax.swing.JFrame, die eine Rendering-Umgebung für Java 3D KAPITEL 4. JSCENEVIEWER3D 53 (a) (b) (c) (d) Abbildung 4.11: Verwendung von Bézier-Patches mit unterschiedlichen Tessellierungsstufen, um eine Krümmung darzustellen. Ein Durchlauf (a), drei Durchläufe (b), sieben Durchläufe (c), zehn Durchläufe (d). beinhaltet. Die Umgebung ist ein Objekt der Klasse J3DCanvas, welche in letzter Instanz für die Darstellung verantwortlich ist. Die folgenden Schritte entsprechen den in Absatz 3.1.1 beschriebenen. Zunächst wird ein virtuelles Universum erzeugt, das die ganze Szene beinhalten wird. Danach wird die Position des Betrachters gesetzt. In weiterer Folge werden der Hintergrund auf eine definierte Farbe gesetzt und Behavior Klassen für die Navigation mit Tastatur und Maus erstellt und initialisiert. Weiters wird der Zähler für die Framerate samt Textausgabe für diese aktiviert. Schließlich wird noch der Content-Branch-Graph aus dem vom Loader generierten Scene Objekt entpackt und in das virtuelle Universum eingefügt. JSceneViewer3D kann in einem finalen Schritt noch die Kompilierung des KAPITEL 4. JSCENEVIEWER3D public void render(BSPVertexLump vertices, BSPMeshvertLump meshverts, BSPTextureLump textures, BranchGroup branchGroup) { [...] switch (type) { [...] case Face.PATCH: BSPVertex[] tessellatedVertices = tessellate(TESSELLATION_LEVEL, vertices); int[] indices = calculateIndices(TESSELLATION_LEVEL); int format = TriangleStripArray.COORDINATES | TriangleStripArray.NORMALS | TriangleStripArray.TEXTURE_COORDINATE_2; int[] trianglesPerRow = new int[TESSELLATION_LEVEL]; for (int i = 0; i < trianglesPerRow.length; i++) { trianglesPerRow[i] = 2 * (TESSELLATION_LEVEL + 1); } TriangleStripArray geometry = new TriangleStripArray(indices.length, format, trianglesPerRow); for (int i = 0; i < indices.length; i++) { geometry.setCoordinate(i, tessellatedVertices[indices[i]].getPosition()); geometry.setNormal(i, tessellatedVertices[indices[i]].getNormal()); if (RenderingOptions.isTextured()) { geometry.setTextureCoordinate(0, i, tessellatedVertices[indices[i]].getTexCoord()); } else { geometry.setColor(i, tessellatedVertices[indices[i]].getColor()); } } Shape3D patch = new Shape3D(geometry, appearance); branchGroup.addChild(patch); break; [...] } } Abbildung 4.12: Das Rendering von Bézier-Patches. Zuerst werden neue Vertices und Indices generiert, anschließend wird der Patch mit einem Triangle-Strip-Array gerendert. 54 KAPITEL 4. JSCENEVIEWER3D 55 Graphen veranlassen, um Optimierungen vorzunehmen. Diese Kompilierung ist standardmäßig aktiviert und wird für gute Ergebnisse auch empfohlen. Somit ist der Szenengraph komplett und wird angezeigt. Das Rendering wird beendet, in dem das entsprechende Fenster geschlossen wird. 4.5 Logger Die Aufzeichnung relevanter Daten ist ein wichtiger Bestandteil von JSceneViewer3D. Die Applikation soll nach der Darstellung der Szene wichtige Daten speichern und dem Benutzer zur Ansicht oder Auswertung zur Verfügung stellen. Dabei hat dieser zwei Möglichkeiten, die Daten einzusehen. Nachdem das Fenster mit der Darstellung geschlossen wurde, werden die Daten einerseits im Logger Tab angezeigt und können andererseits dort per Knopfdruck in eine Log-Datei gespeichert werden. Um die zweite Möglichkeit zu bieten, verwendet die für das Logging verantwortliche Klasse JSV3DLogger Elemente des bewährten Frameworks log4j 4 . 4.5.1 log4j Bei log4j handelt es sich um ein so genanntes Logging-Framework, also eine Software-Komponente, die es erlaubt, während der Ausführung eines Programms Meldungen über den Status, mögliche Probleme oder andere relevante Informationen auszugeben. Dies kann notwendig werden, falls es nicht möglich ist, eine Anwendung zu debuggen (Fehler zu suchen und zu beseitigen), etwa weil eine Ausführung mit Debugger zu langsam ist oder weil viele parallele Threads das Debugging erschweren. Logging-Einträge müssen nur einmal an den relevanten Stellen im Quellcode platziert werden und geben zum Zeitpunkt des Aufrufs die entsprechenden Informationen ohne späteres Zutun eines Entwicklers aus. Natürlich ist auch Logging nicht ohne Nachteile: Werden zu viele Einträge gesetzt, so kann die Applikation verlangsamt werden. Zu viele generierte Log-Einträge schaden unter Umständen auch der Übersicht. Die Handhabung von log4j ist bewusst einfach gestaltet. Das Framework besteht aus drei Hauptteilen, die hierarchisch miteinander verbunden sind. An der Spitze steht die Klasse org.apache.log4j.Logger. Fast alle Zugriffe auf log4j Funktionen erfolgen über sie. Jeder Logger besitzt einen Level (d. h. eine Stufe). Dieser bestimmt, ab welchem Wichtigkeitsgrad Meldungen ausgegeben werden. Die verfügbaren Levels (in aufsteigender Reihenfolge der Wichtigkeit geordnet) sind: DEBUG - INFO - WARN - ERROR - FATAL. Wird also ein Logger mit dem Level INFO angelegt, gibt er nur Meldungen mit mindestens dieser Priorität aus (d. h. alle, außer als DEBUG 4 http://logging.apache.org/log4j/ KAPITEL 4. JSCENEVIEWER3D 56 klassifizierte Meldungen werden in diesem Fall ausgegeben). Um nun LogMeldungen abzusetzen (und gleichzeitig zu klassifizieren), stellt die Klasse die folgenden fünf wichtigen Methoden zur Verfügung: debug(Object message), info(Object message) und warn(Object message) für leichtere Fehler, sowie error(Object message) und fatal(Object message) für schwerere Fehler. Als Parameter kann jedes beliebige Objekt mitgegeben werden. org.apache.log4j.Logger beinhaltet zunächst jedoch noch keinerlei Information, wie die Ausgabe der Log-Meldungen erfolgen soll. log4j stellt dafür die Schnittstelle org.apache.log4j.Appender zur Verfügung. Klassen, die diese Schnittstelle implementieren, bieten die Möglichkeit, die vom Logger gesammelten Daten auf ein bestimmtes Medium auszugeben. Solche Medien können die Konsole einer Applikation, eine Datei auf der Festplatte oder auch eine entfernte Netzwerk-Ressource sein. Jeder Logger kann dabei eine beliebige Anzahl an Appendern verwalten. Sobald eine Log-Meldung erzeugt wird, wird sie an alle beim Logger registrierten Appender-Klassen weitergeleitet und von diesen entsprechend ausgegeben. Das Aussehen der Ausgabe bestimmt der dritte Teil der log4j Hierarchie – die Klasse org.apache.log4j.Layout. Mit ihrer Hilfe können LogMeldungen individuell formatiert und an die Bedürfnisse der jeweiligen Applikation angepasst werden. log4j beinhaltet bereits einige Layouts, um Daten in HTML, XML oder als einfachen Text auszugeben. Details zur Architektur von log4j sowie eine Vielzahl an Anwendungsbeispielen gibt [7]. 4.5.2 Implementierung des Loggers Die zuvor bereits erwähnte Klasse JSV3DLogger enthält einerseits eine Instanz eines log4j Logger-Objekts zur Ausgabe der Daten in eine Datei und andererseits Datenkomponenten zur internen Speicherung der gemessenen Werte. Gespeichert werden Startzeit, Endzeit, Gesamtdauer, durchschnittliche Frames pro Sekunde sowie die Anzahl der Vertices und Polygone in einem Level. Angelegt wird die Instanz des JSceneViewer3D Loggers mit einem Dateinamen als Parameter. Jener bestimmt, wie die zu schreibende Log-Datei benannt werden soll. Alternativ kann für den Parameter auch null als Wert übergeben werden. In diesem Fall wird keine Datei angelegt, sondern es werden alle Meldungen auf der Konsole ausgegeben. Der Logger wird mit der Stufe INFO angelegt, in der momentanen Implementierung kommen keine anderen Stufen zum Einsatz. Als Appender wird die selbst entwickelte Klasse BufferedAppender verwendet. Die Notwendigkeit für eine eigene Implementierung entstand durch den Umstand, dass es dem Benutzer obliegen soll, ob eine Log-Datei geschrieben wird oder nicht. Alle mit log4j mitgelieferten Appender verfolgen jedoch die Strategie, erzeugte Meldungen sofort auszugeben. Daher wurde die Klasse Buf- KAPITEL 4. JSCENEVIEWER3D 57 feredAppender entwickelt. Ihre Aufgabe ist es, alle vom Logger abgegebenen Meldungen aufzunehmen und in einem Puffer zu speichern. An die Klasse können weitere Appender angehängt werden, die dann die eigentliche Ausgabe auf ein Medium übernehmen. Diese wird durch die Methode writeLogs() angeregt. Alle im Puffer gespeicherten Meldungen werden somit an die angehängten Appender weitergeleitet und der Zwischenspeicher wird anschließend geleert. Auf diese Art und Weise kann die Erzeugung der Log-Datei genau kontrolliert werden. 4.5.3 Aufrufe des Loggers Mit dem Öffnen einer Level-Datei wird der Logger angelegt. Sobald das Rendering gestartet wird, erfolgt die Setzung der Startzeit, und das LoggerObjekt wird an den Level-Loader übergeben. Nachdem jener die Geometrie geladen hat, wird die Anzahl der Polygone und Vertices ausgelesen und im Logger gespeichert. Mit der Anzeige des Darstellungsfensters wird die Instanz des Loggers an dieses weiter gegeben. Sobald das Fenster wieder geschlossen wird, d. h. wenn die Darstellung beendet wurde, liest der Logger die errechnete durchschnittliche Framerate aus und speichert sie zusammen mit der Endzeit und der Gesamtzeit. Im Anschluss werden die soeben gesammelten Informationen im LoggerTab eingefügt. Nun kann der Benutzer noch mit einem Klick auf Save Logs“ ” eine Log-Datei auf die Festplatte schreiben. Dies schließt einen kompletten Rendering- und Auswertungs-Vorgang ab. Kapitel 5 Auswertung der Performance-Daten Nach Abschluss der Implementierung von JSceneViewer3D wurden Tests zur Messung der Performance (Performanz, Leistungsmerkmale) durchgeführt. Diese sollen darstellen, wie sich die Applikation mit verschiedenen Levels und unterschiedlichen Parametern verhält. Nicht zuletzt sollen diese Daten auch darüber Auskunft geben, ob die Umsetzung mit Java geeignet erscheint oder nicht. 5.1 Test-Voraussetzungen und Ablauf Alle Tests wurden auf dem selben Testsystem durchgeführt. Die verwendete Hardware bestand aus einem AMD Athlon 64 3200+ (2.00 GHz) Prozessor und einer MSI NX6800 (GeForce 6800 Chipsatz, PCIe, 256 MB DDR RAM) Grafikkarte mit der Treiberversion ForceWare 84.21. Das verwendete Betriebssystem war Microsoft Windows XP mit Service Pack 2. Weiters wurden der Java Development Kit der Java 2 Platform Standard Edition (J2SE) 5.0 Update 7 sowie Java 3D 1.4.0 Update 1 verwendet. Zum Zeitpunkt der Tests wurden alle anderen Anwendungen im System deaktiviert. Für die Tests mit JSceneViewer3D wurden fünf verschiedene Levels mit verschieden hoher Polygonanzahl getestet. Pro Level wurde der Test jeweils mit der niedrigsten und der höchsten verfügbaren Auflösung (640 × 480 bzw. 1280 × 1024 Pixel) durchgeführt. Weiters wurde jeweils sowohl mit als auch ohne kompiliertem Szenengraphen getestet. Bei den einzelnen Durchläufen wurde durch jeden Level eine Minute lang hindurch navigiert. Dabei wurde darauf geachtet, möglichst alle Teile der Welt zu besuchen und trotz fehlender Kollisionserkennung weitestgehend auf den begehbaren Wegen und Pfaden zu verweilen. Da aus Zeitgründen kein automatisiertes Durchlaufen des Levels implementiert werden konnte, musste die Navigation per Hand“ erfolgen. Dadurch ergaben sich zwangsläufig ” 58 KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN 59 Ungenauigkeiten, da es so nicht möglich war, immer exakt die gleichen Wege zu gehen. Es wurde daher versucht, diesen Umstand zu kompensieren, indem in jedem Testmodus fünf Durchläufe hintereinander durchgeführt wurden. Für jeden Testlauf wurde die Applikation neu gestartet, um keine Verzerrungen durch überhöhten Speicherverbrauch zuzulassen. Unter diesen Voraussetzungen ergaben sich somit insgesamt 100 Testläufe mit JSceneViewer3D (Fünf Levels, zwei verschiedene Auflösungen, Kompilierung des Szenengraphen an bzw. aus – ergibt zwanzig Modi mit je fünf Durchläufen). Pro Modus wurde ein Testlauf mit einer Webcam, die an einem eigenständigen Rechner betrieben wurde, gefilmt. Die entstandenen zwanzig Videos liegen dieser Arbeit bei. Zusätzlich wurde nach jedem einzelnen Durchlauf die generierte Log-Datei gespeichert. Die so entstandenen 100 Dateien liegen ebenfalls bei (siehe Anhang B). Um die erhaltenen Werte von JSceneViewer3D vergleichen zu können, wurden die selben Messungen ebenfalls mit dem Originalspiel Quake III Arena durchgeführt. Verwendet wurde Quake III Arena 1.32 mit der Challenge Pro Mode Arena (CPMA) Modifikation. Wiederum wurden die selben fünf Levels mit den selben Auflösungen jeweils fünf Mal getestet. Da die Kompilierung des Szenengraphen auf Quake III selbstverständlich nicht anwendbar ist, halbierten sich die Testläufe auf 50. Um die Daten zu messen und auch in Log-Dateien speichern zu können, wurde die Software FRAPS 1 verwendet. Diese erlaubt es, ähnlich wie JSceneViewer3D, die durchschnittlichen Frames pro Sekunde sowie die Dauer des Messvorgangs zu berechnen und zu protokollieren. Lediglich die Polygon- und Vertexanzahl wird nicht gemessen. Da es sich jedoch um die selben Welten handelt, konnten die bereits von JSceneViewer3D ermittelten Daten verwendet werden. Die von FRAPS generierten Log-Dateien sowie die ebenfalls mit der externen Webcam aufgezeichneten Videos liegen der Arbeit bei. Um den Vergleich der Levels zwischen JSceneViewer3D und Quake III zu ermöglichen, musste letzteres im Vorfeld noch angepasst werden. Folgende Änderungen wurden durchgeführt, um den Funktionsumfang der Darstellung von Quake III dem von JSceneViewer3D anzugleichen. Die Werte in den eckigen Klammern bezeichnen die Konsolebefehle, die in Quake III eingegeben werden müssen, um das jeweilige Verhalten zu erzielen. Neue Karte laden und Cheatmodus aktivieren (nötig, um die Kollisionserkennung zu deaktivieren) [\devmap kartenname] Heads Up Display (Anzeige der Gesundheit, der verfügbaren Munition usw.) entfernen [\cg_draw2d 0] Modell der Waffe ausblenden [\cg_drawGun 0] Fadenkreuz ausblenden [\cg_drawCrosshair 0] 1 http://www.fraps.com/ KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN 60 Schatten deaktivieren [\cg_drawShadows 0] Von Vollbild- auf Fenstermodus wechseln [\r_fullscreen 0] Alle Entitäten in der Szene (Bonusgegenstände, Munition usw.) ausblenden [\r_drawEntities 0] Maximale Anzahl an Frames pro Sekunde von den voreingestellten 90 auf 999 setzen [\com_maxfps 999] Kollisionserkennung deaktivieren und den ”freien Schwebemodus“ aktivieren [\noclip 1] 5.1.1 Die getesteten Levels Die fünf verwendeten Welten stammen alle aus der Challenge Pro Mode Arena Modifikation. Sie haben den Vorteil, dass pro PK3-Datei genau ein Level enthalten ist, und sie sich daher leicht verarbeiten lassen. Folgende Levels kamen bei den Tests zum Einsatz: cpm24 – phrantic: 1031 Polygone, 5528 Vertices, dargestellt in Abbildung 5.1. cpm25 – Ptolemy’s Wrath: 1767 Polygone, 11662 Vertices, dargestellt in Abbildung 5.2. cpm4a – Realm of Steel Rats: 2835 Polygone, 16981 Vertices, dargestellt in Abbildung 5.3. cpm19 – Q3JDM9: 4634 Polygone, 25025 Vertices, dargestellt in Abbildung 5.4. cpm13 – Abusive Intentions: 8815 Polygone, 40203 Vertices, dargestellt in Abbildung 5.5. 5.2 Tests mit JSceneViewer3D Im Folgenden werden die gemessenen Ergebnisse der Performance Tests mit JSceneViewer3D tabellarisch dargestellt. Die in den Tabellen-Kopfzeilen verwendeten Abkürzungen stehen dabei für den jeweils verwendeten Modus. 640 C entspricht einer Auflösung von 640 × 480 Pixel mit kompiliertem Szenengraphen (compiled). NC steht für nicht kompiliert (not compiled). Tabelle 5.1 veranschaulicht die Ergebnisse der Tests mit der kleinsten Karte cpm24. Auffällig hierbei ist vor allem, dass die Tests mit nicht kompiliertem Szenengraphen bessere Ergebnisse erzielen. Bei 640 × 480 Pixel liegt der Geschwindigkeitsgewinn bei etwa 3 %, bei 1280 × 1024 Pixel sind es bereits knapp 7 %. KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN Abbildung 5.1: Ein Screenshot des Levels cpm24. Abbildung 5.2: Ein Screenshot des Levels cpm25. 61 KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN Abbildung 5.3: Ein Screenshot des Levels cpm4a. Abbildung 5.4: Ein Screenshot des Levels cpm19. 62 KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN Abbildung 5.5: Ein Screenshot des Levels cpm13. Tabelle 5.1: Die Ergebnisse der Messungen des Levels cpm24 in Frames pro Sekunde (JSceneViewer3D). 640 C 640 NC 1280 C 1280 NC Test 1 402,54 398,57 161,88 150,33 Test 2 397,45 411,46 151,56 150,96 Test 3 480,28 476,13 146,96 183,69 Test 4 397,30 435,34 160,00 176,29 Test 5 387,52 405,16 143,45 155,66 Schnitt 413,02 425,33 152,77 163,39 63 KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN 64 Tabelle 5.2: Die Ergebnisse der Messungen des Levels cpm25 in Frames pro Sekunde (JSceneViewer3D). 640 C 640 NC 1280 C 1280 NC Test 1 399,93 341,74 146,54 149,18 Test 2 419,16 386,54 162,46 157,68 Test 3 315,31 405,81 155,97 157,44 Test 4 389,18 403,90 200,94 135,26 Test 5 387,31 414,55 142,98 181,04 Schnitt 382,18 390,51 161,78 156,12 Tabelle 5.3: Die Ergebnisse der Messungen des Levels cpm4a in Frames pro Sekunde (JSceneViewer3D). 640 C 640 NC 1280 C 1280 NC Test 1 289,54 337,43 124,13 138,95 Test 2 316,01 323,56 134,82 139,28 Test 3 300,57 354,62 127,88 128,60 Test 4 344,13 265,25 142,71 139,64 Test 5 325,52 315,54 126,30 132,25 Schnitt 315,15 319,28 131,17 135,74 Tabelle 5.2 beleuchtet die Ergebnisse der Karte cpm25. Verglichen mit cpm24 kommen wesentlich mehr Vertices auf ein Polygon. Bei der niedrigeren Auflösung ist wiederum die nicht kompilierte Version des Szenengraphen schneller, bei der größeren Auflösung ist das Ergebnis umgekehrt. Die Unterschiede bewegen sich jedoch im niedrigen Prozentbereich und sind weitgehend vernachlässigbar. Auch bei der Auswertung des Levels cpm4a zeigt sich, dass die Testergebnisse der nicht kompilierten Version um wenige Frames pro Sekunde besser als die der kompilierten sind. Verglichen zur polygonärmsten Karte nimmt die durchschnittliche Framerate bei 640 × 480 Pixel bereits um ca. 100 Frames pro Sekunde ab, bei 1280 × 1024 beträgt die Abnahme lediglich etwa 20 Frames pro Sekunde. Die in Tabelle 5.4 dargestellten Daten der Karte cpm19 zeigen erneut ein differenziertes Bild. In der niedrigen Auflösung ist wiederum der unveränderte, nicht kompilierte Szenengraph performanter, in der hohen Auflösung erreicht der kompilierte Graph etwa 3 Frames pro Sekunde mehr. KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN 65 Tabelle 5.4: Die Ergebnisse der Messungen des Levels cpm19 in Frames pro Sekunde (JSceneViewer3D). 640 C 640 NC 1280 C 1280 NC Test 1 295,36 315,11 128,91 142,82 Test 2 313,68 311,89 155,27 137,45 Test 3 322,40 350,54 142,63 141,48 Test 4 301,12 299,51 144,97 146,35 Test 5 304,86 315,23 148,84 136,96 Schnitt 307,48 318,46 144,12 141,01 Tabelle 5.5: Die Ergebnisse der Messungen des Levels cpm13 in Frames pro Sekunde (JSceneViewer3D). 640 C 640 NC 1280 C 1280 NC Test 1 249,09 181,70 89,97 118,17 Test 2 213,25 249,39 119,72 89,70 Test 3 241,75 212,26 111,70 120,68 Test 4 221,14 237,02 122,07 97,92 Test 5 207,16 203,19 106,43 120,68 Schnitt 226,48 216,76 109,98 109,43 Bei cpm13, der größten aller Karten, tritt zum ersten Mal der Fall ein, dass die kompilierte Version des Szenengraphen in beiden Auflösungen bessere Ergebnisse liefert. Sind es bei 640 × 480 Pixel noch etwa zehn Frames pro Sekunde, so beträgt der Unterschied bei 1280 × 1024 Pixel gerade noch einen halben Frame pro Sekunde. Tabelle 5.5 veranschaulicht dies. 5.3 Tests mit Quake III Arena Im Folgenden werden die Ergebnisse des Performance Tests mit Quake III dargestellt. Die Werte in den Kopfzeilen der Tabellen repräsentieren die jeweils verwendete Auflösung. 640 entspricht 640 × 480 Pixel, 1280 steht für 1280 × 1024 Pixel. Die in Tabelle 5.6 abgebildeten Ergebnisse der Karte cpm24 zeigen eine konstante Framerate über alle fünf Tests hinweg. Bei der niedrigen Auflösung ist die Framerate mehr als dreimal so hoch als bei der hohen Auflösung. Tabelle 5.7 stellt die Ergebnisse der Karte cpm25 dar. Obwohl diese KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN 66 Tabelle 5.6: Die Ergebnisse der Messungen des Levels cpm24 in Frames pro Sekunde (Quake III). 640 1280 Test 1 785,95 222,55 Test 2 776,44 228,20 Test 3 774,46 228,46 Test 4 784,61 230,53 Test 5 748,40 226,45 Schnitt 773,97 227,23 Tabelle 5.7: Die Ergebnisse der Messungen des Levels cpm25 in Frames pro Sekunde (Quake III). 640 1280 Test 1 849,74 241,93 Test 2 846,58 239,59 Test 3 850,65 240,50 Test 4 854,72 242,09 Test 5 845,09 242,39 Schnitt 849,36 241,30 mehr Polygone und Vertices als cpm24 enthält, wird sowohl in der niedrigen als auch in der hohen Auflösung eine deutlich höhere Framerate erreicht. Verantwortlich dafür ist die Architektur des Levels – cpm25 enthält mehr enge Gänge, während cpm24 aus einem großen Raum mit einigen Abteilern besteht. Je weniger Geometrie sichtbar ist, desto weniger Sichtbarkeitsberechnungen sind im BSP-Baum nötig. Entsprechend höher ist auch die Framerate. Die Karte cpm4a zeigt ein ähnliches Bild: Eine sehr hohe Framerate in der niedrigen Auflösung, in der hohen wird noch etwa ein Drittel dieses Werts erreicht. Tabelle 5.8 enthält die genauen Werte. In Tabelle 5.9 sind die Ergebnisse des zweitgrößten Levels dargestellt. Auffallend ist der Einbruch der Framerate bei 640 × 480 Pixel. Diese liegt hier knapp 200 Frames pro Sekunde unter denen der restlichen Levels und ist somit nur noch doppelt so hoch wie bei 1280 × 1024 Pixel. Die Werte der komplexesten Karte cpm13 unterscheiden sich nur geringfügig von denen anderer Karten. Trotz der vielen Polygone und Vertices ist KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN Tabelle 5.8: Die Ergebnisse der Messungen des Levels cpm4a in Frames pro Sekunde (Quake III). 640 1280 Test 1 760,49 232,30 Test 2 759,51 231,98 Test 3 776,47 229,18 Test 4 741,73 232,11 Test 5 750,90 226,84 Schnitt 757,82 230,48 Tabelle 5.9: Die Ergebnisse der Messungen des Levels cpm19 in Frames pro Sekunde (Quake III). 640 1280 Test 1 493,16 220,01 Test 2 536,78 227,55 Test 3 551,49 224,09 Test 4 495,26 224,98 Test 5 548,09 227,32 Schnitt 524,96 224,79 Tabelle 5.10: Die Ergebnisse der Messungen des Levels cpm13 in Frames pro Sekunde (Quake III). 640 1280 Test 1 673,43 233,36 Test 2 787,54 227,66 Test 3 712,64 228,68 Test 4 679,90 225,61 Test 5 737,81 229,90 Schnitt 718,26 229,04 67 KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN 68 kein Leistungseinbruch zu erkennen. Verantwortlich ist wiederum das Design der Karte. Viele einzelne durch Gänge verbundene Räume begrenzen die Anzahl der sichtbaren Polygone. Tabelle 5.10 liefert die gemessenen Zahlen. 5.4 Vergleich und Fazit Vergleicht man zunächst nur die von JSceneViewer3D generierten Daten, so fällt auf, dass durch die Kompilierung des Szenengraphen keinerlei Geschwindigkeitsvorteile entstehen. Es tritt sogar eher das Gegenteil ein: In sechs von zehn Fällen erweist sich die nicht kompilierte Version als performanter. Dies lässt mehrere Interpretationsmöglichkeiten zu: Eine Erklärung dafür ist, dass die Geometriedaten des Quake III Levels bereits sehr optimiert vorliegen. Es werden nur die Vertices gespeichert, die auch tatsächlich für die Bildung von Polygonen benötigt werden. Auch bei der Tessellierung zur Generierung von gekrümmten Flächen werden nur die nötigsten Vertices erstellt. Möglicherweise ist jedoch auch die Geometrie bereits zu komplex, um weitreichende Optimierungen vornehmen zu können. Beim Vergleich der Daten von JSceneViewer3D und Quake III (Abbildung 5.6 und Abbildung 5.7) fällt sofort auf, dass letzteres mit wesentlich besseren Werten aufwarten kann. In der Regel ist die erzielte Framerate etwa doppelt so hoch wie die von JSceneViewer3D. Dies gilt sowohl für die niedrige Auflösung mit 640 × 480 Pixel als auch für die hohe mit 1280 × 1024 Pixel. Die Zahlen sprechen somit eindeutig für die in C realisierte QuakeEngine, Java 3D hat in allen Fällen das Nachsehen. Allerdings sollte nicht nur anhand der vorliegenden Zahlen geurteilt werden. Frameraten jenseits der 700 Frames pro Sekunde sind zwar beeindruckend, jedoch keinesfalls notwendig. Im Normalfall begrenzt Quake III die Framerate sogar auf 90 Frames pro Sekunde. Dies lässt eine flüssige Darstellung zu und vermeidet unnötige Render-Vorgänge. Eine höhere Framerate als die Bildwiederholfrequenz des Monitors ist ebenfalls nur bedingt sinnvoll. Erfolgt die Darstellung wie in Tabelle 5.7 angeführt etwa mit über 800 Frames pro Sekunde, so liefert die Grafikkarte in der Sekunde 800 Bilder an den Monitor. Wird dieser mit 60 Hertz betrieben (so wie es beim vorliegenden Testsystem der Fall war), kann er jedoch nur 60 Bilder in der Sekunde darstellen. Das bedeutet, dass während des Bildaufbaus des Monitors der Bild- oder Pufferinhalt gewechselt wird. Der Monitor stellt also ab einer gewissen Zeile bereits das nächste Bild dar und der Benutzer sieht ein Ergebnis, das aus mehreren verschiedenen Einzelbildern besteht. Für das Auge unangenehme Schlieren (so genannte Glitches) sind die Folge, da die einzelnen Abschnitte nicht genau zusammenpassen. Abhilfe dagegen schafft die Aktivierung der vertikalen Synchronisation (des so genannten VSyncs). Dabei sendet die Grafikkarte nur so viele Bilder, wie der Monitor verarbeiten kann. Dies würde in die- KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN 69 Abbildung 5.6: Der Vergleich der fünf getesten Levels in der Auflösung 640 × 480 Pixel zwischen Quake III (Q3), JSceneViewer3D mit nicht kompiliertem Szenengraphen (NC) und JSceneViewer3D mit kompiliertem Szenengraphen (C). Die Werte sind in Frames pro Sekunde angegeben. Abbildung 5.7: Der Vergleich der fünf getesten Level in der Auflösung 1280 × 1024 Pixel zwischen Quake III (Q3), JSceneViewer3D mit nicht kompiliertem Szenengraphen (NC) und JSceneViewer3D mit kompiliertem Szenengraphen (C). Die Werte sind in Frames pro Sekunde angegeben. sem Fall etwa eine Begrenzung der Framerate auf 60 Frames pro Sekunde bedeuten. Führt man sich diese Thematik also vor Augen, so sind die bei Quake III gemessenen Frameraten nur bis zu einem gewissen Punkt sinnvoll. Da JSceneViewer3D selbst bei der komplexesten Karte in der höchsten Auflösung noch eine Framerate von ca. 110 Frames pro Sekunde erreicht (Tabelle 5.5), kann dies durchaus als ausreichend angesehen werden. Bezieht man weiters den Umstand mit ein, dass JSceneViewer3D aufgrund des – verglichen zu Quake III – äußerst kurzen Entwicklungszeitraums sicherlich noch nicht KAPITEL 5. AUSWERTUNG DER PERFORMANCE-DATEN 70 perfekt für die Darstellung von Quake III Welten optimiert ist, so könnten durchaus noch bessere Ergebnisse erzielt werden. Aufgrund der in diesem Kapitel ausgewerteten Performance-Daten lässt sich zusammenfassend sagen, dass die derzeit vorliegende Version von JSceneViewer3D durchaus Leistungsdaten aufweist, die als geeignet für die Darstellung dieser komplexen 3D-Welten angesehen werden können. Der Abstand zu den mit Quake III erzielten Daten ist zwar groß, liegt jedoch in einem Bereich, in dem die fehlenden Frames pro Sekunde noch keine Auswirkungen auf eine flüssige Darstellung haben. Die Kompilierung des Szenengraphens in Java 3D (Compiled Retained Mode) konnte keine spürbaren Verbesserungen gegenüber der nicht kompilierten Version (Retained Mode) erzielen. Eine Optimierung des Programmcodes kann jedoch mit Sicherheit die Leistung weiter verbessern. Kapitel 6 Evaluierung und Schlussbemerkungen Nach Abschluss der Implementierung der Applikation und der Auswertung der damit erfolgten Testläufe soll nun versucht werden, eine Antwort auf die eingangs gestellte Frage: Eignet sich Java zur Spiele-Entwicklung?“ zu ” geben. Vorweg muss dabei erwähnt werden, dass diese Fragestellung keineswegs mit einem einfachen Ja“ oder Nein“ zu beantworten ist. Zu vielfältig ” ” sind die unterschiedlichen Faktoren, die auf das Ergebnis einwirken. 6.1 Evaluierung Die Evaluierung von Java für den Einsatz in der Spiele-Entwicklung soll zunächst aus Gründen der Übersicht in einige wenige große Bereiche eingeteilt werden: Verfügbarkeit von Software, Produktivität und Performance. 6.1.1 Verfügbarkeit von Software Ausschlaggebend für die Entscheidung, ob ein Software Projekt (d. h. in diesem Fall ein Spiel) auf einer bestimmten Plattform (hier Java) umgesetzt wird bzw. werden kann, ist die Verfügbarkeit entsprechender Software und Softewarekomponenten. In der Spiele-Entwicklung sind dabei die klassischen fünf Hauptkomponenten Grafik, Sound, Netzwerk, Physik und künstliche Intelligenz maßgebend. Miteinbezogen werden müssen jedoch auch die Verfügbarkeit von Laufzeit-Umgebungen und Entwicklungswerkzeugen. Die Verfügbarkeit von Grafik-Bibliotheken und so genannter Middleware (Engines) für Java wurde in Kapitel 3 ausführlich behandelt. Die sieben beschriebenen Produkte zeigen, dass eine rege Aktivität auf diesem Sektor herrscht und dass dem Entwickler gleichzeitig eine gute Auswahlmöglichkeit geboten wird, wenn es um die Selektion der zu verwendenden GrafikBibliotheken geht. 71 KAPITEL 6. EVALUIERUNG UND SCHLUSSBEMERKUNGEN 72 Bei der Implementierung von Sound (Musik und Geräusche) steht dem Entwickler seit Java Version 1.3 die Java Sound API zur Verfügung. Mit Hilfe der Klassen der Pakete javax.sound.midi und javax.sound.sampled kann eine Vielzahl an Formaten wiedergeben werden. Um komplexere Audiobearbeitungen durchzuführen, steht weiters noch das Java Media Framework1 zur Verfügung. Viele von dieser Komponente gebotenen Funktionalitäten werden jedoch in einem Spiel wohl nur selten benötigt. Schließlich gibt es noch Java-Anbindungen für bekannte Sound-Bibliotheken wie etwa FMOD oder OpenAL. Solche sind zum Beispiel in LWJGL (siehe Abschnitt 3.1.2) vorhanden. Im Bereich der Netzwerk-Implementierung für Spiele stehen in Java von Beginn an zahlreiche Klassen in der Standardbibliothek zur Verfügung. Gosling nennt in [8] die Einfachheit im Umgang mit Netzwerk- Zugriffen einen großen Vorteil von Java gegenüber C oder C++. Auch für Physik-Simulationen gibt es mit Odejava2 eine Möglichkeit, diese in Java zu realisieren. Darüber hinaus stehen jedoch kaum Bibliotheken zur Verfügung. Für die Realisierung künstlicher Intelligenz gibt es generell kaum fertige Lösungen, weshalb eine Implementierung in der Regel selbst vorgenommen werden muss. Java eignet sich hierbei aufgrund seiner guten Effizienz (siehe auch Abschnitt 6.1.2) besonders für diese Aufgabe. Einige in Abschnitt 3.2 aufgelisteten Spiele machen von diesem Modell Gebrauch. Die Verfügbarkeit von Laufzeitumgebungen ist in Java sehr gut. Das Java Runtime Environment (JRE) ist frei verfügbar und wird regelmäßig aktualisiert. In [2] wird erwähnt, dass die Firma Sun Microsystems davon ausgeht, dass etwa 50 Prozent aller neu ausgelieferten PCs bereits ein JRE installiert haben. Eine Übersicht über weitere verfügbare Java Laufzeitumgebungen bietet [11]. Betrachtet man schließlich noch die verfügbaren Entwicklungsumgebungen für Java, so gibt es auch hier eine Reihe von verfügbarer Software, die bei der Spiele-Entwicklung eingesetzt werden kann. eclipse3 stellt die zur Zeit wohl bekannteste und meist verwendete Umgebung dar. Das Programm ist zudem frei verfügbar. Weitere (kommerzielle) Produkte sind IntelliJIDEA4 und Borland JBuilder5 . Die Verfügbarkeit von Softwarekomponenten in Java ist also durchaus gut, wenngleich natürlich Entwicklern in C oder C++ noch wesentlich mehr Produkte zur Verfügung stehen. Auch die Tatsache, dass viele Java-Implementierungen lediglich Anbindungen an C oder C++ Bibliotheken sind, darf nicht außer Acht gelassen werden. Anbindungen werden in der Regel erst 1 http://java.sun.com/products/java-media/jmf/ http://odejava.org/ 3 http://www.eclipse.org/ 4 http://www.jetbrains.com/idea/ 5 http://www.borland.com/de/products/jbuilder/ 2 KAPITEL 6. EVALUIERUNG UND SCHLUSSBEMERKUNGEN 73 mit einer gewissen Verzögerung auf den technischen Stand ihres Vorbilds“ ” gebracht. 6.1.2 Produktivität Produktivität ist ein entscheidender Faktor bei der Realisierung von Software-Projekten. Je effizienter Entwickler arbeiten können, desto mehr Zeit kann in die Umsetzung des Projekts investiert werden, da weniger Zeit für andere Dinge, wie die Lösung technischer oder sprachspezifischer Probleme, aufgewandt werden muss. Zahlreiche Artikel wie etwa [12], [22] oder [24] erwähnen die hohe Produktivität, die Entwickler durch die Verwendung von Java erreichen können. Als Hauptgründe werden vor allem die Plattformunabhängigkeit, einfache Speicherverwaltung, die große, mitgelieferte Standardbibliothek und das einfache, objektorientierte Konzept genannt. Auch Stabilität ist ein wichtiger Faktor zur Produktionssteigerung. Die Java-Laufzeitumgebungen enthalten sehr wenige Fehler, weshalb es in der Regel kaum zu nicht nachvollziehbaren Abstürzen kommt. Fehler, die durch den Entwickler verursacht wurden, werden durch das Konzept der JavaExceptions (Ausnahmen) gut sichtbar gemacht und sind dadurch leichter nachzuvollziehen. Kryptische Fehlermeldungen, wie etwa ein Speicherzugriffsfehler an einer bestimmten Adresse, treten in Java nicht auf. Auch das Vorhandensein einer guten Dokumentation bewirkt eine Steigerung der Effizienz. Die Java-Standardbibliothek ist durchgehend und sehr ausführlich dokumentiert, die Dokumentation des API ist im Internet abrufbar. Auch für selbst erstellte Applikationen kann auf einfache Art und Weise aus dem dokumentierten Quellcode eine Dokumentation generiert werden. Bei der Implementierung von JSceneViewer3D wurden alle soeben genannten Faktoren zur Produktivitätssteigerung beobachtet. Neben diversen Funktionalitäten der Standardbibliothek wurden mit Java 3D, JIMI und log4j lediglich drei externe Bibliotheken benötigt. Deren Einbindung in das Projekt gestaltete sich aufgrund der guten Dokumentation äußerst einfach. Für die Erstellung der grafischen Benutzeroberfläche etwa war keine externe Bibliothek notwendig. Durch die Java-Speicherverwaltung konnte von Beginn an eine Applikation ohne grobe Speicherlecks (Memory Leaks) erzeugt werden. Auch Programmabstürze waren zumeist schnell behebbar. Im Falle der Interaktion des Benutzers mit dem GUI konnten aufgrund des Ausnahmen-Systems auf einfache Art und Weise Rückmeldungen generiert werden, die dem Benutzer abseits technischer Details erklärten, welcher Fehler aufgetreten war. Die Verwendung von Java 3D im (Compiled) Retained Mode erwies sich als sehr effektiv. Es war lediglich notwendig, den Szenengraphen mit Hilfe des selbst erstellten Level-Loaders zu erzeugen. Der eigentliche Rendervorgang musste nicht implementiert werden. Auch Techniken zur Optimierung der Leistung, wie etwa View-Frustum-Culling oder Sichtbarkeitsberechnungen KAPITEL 6. EVALUIERUNG UND SCHLUSSBEMERKUNGEN 74 (siehe Abschnitt 2.3.2), brauchten nicht selbst realisiert zu werden, da Java 3D diese Algorithmen bereits bei der Darstellung anwendet. 6.1.3 Performance Performance von Java-Applikationen ist seit Erfindung dieser Programmiersprache ein heikles und viel diskutiertes Thema. Der generelle Konsens lautet, dass Java-Programme in Punkto Performance C oder C++ Programmen unterlegen sind. Jedoch gibt es auch Artikel, die durch Tests belegen, dass es kaum einen Unterschied gibt bzw. Java sogar schneller sein kann. Beispiele hierfür sind [13] oder [14]. Die in Kapitel 5 gelisteten Ergebnisse zeigen klar und deutlich, dass die vorliegende Implementierung, an den Leistungsdaten gemessen, nicht mit der Original-Applikation mithalten kann. Dies ist zum Teil auf Java 3D zurückzuführen, welches im Compiled Retained Mode zwar selbst Optimierungen vornimmt, den Benutzer aber mit Ausnahme der konfigurierbaren Parameter keinerlei Veränderungen oder Verbesserungen an der Darstellung vornehmen lässt. Nicht unwesentlich ist jedoch auch die Tatsache, dass der vorliegende Code nicht explizit auf Performance ausgelegt (d. h. getweaked) wurde. So sind sicherlich an einigen Stellen noch Verbesserungen möglich, die aus zeitlichen Gründen aber ausbleiben mussten. Dennoch bewegen sich die erreichten Frameraten in einem Bereich (über 100 Frames pro Sekunde), der als durchaus gut angesehen werden kann. 6.2 Fazit Basierend auf den soeben vorgestellten drei Faktoren spricht vor allem die hohe Produktivität für eine Entwicklung eines Spiels in Java. Die Verfügbarkeit von Softwarekomponenten und die erreichbare Performance sind differenziert zu sehen. Vor allem für kleinere Applikationen, wie etwa die vorliegende Version von JSceneViewer3D, reichen die vorgestellten und verwendeten Bibliotheken aus. Es lässt sich damit auch eine akzeptable Performance bei der Umsetzung erreichen, ohne viel Code Tweaking durchführen zu müssen. Für umfangreichere Applikationen (wie etwa ein voll implementiertes Spiel) kann es unter Umständen zu Performance-Engpässen kommen, vor allem wenn keine Code-Optimierungen durchgeführt wurden. In diesem Fall wird auch die Einbindung von nativem C/C++ Code nicht immer vermieden werden können. Dies bedeutet ein Arbeiten auf niedrigerem Level und verringert wiederum die Produktivität, da die Grundkonzepte von Java hier nicht mehr zur Anwendung kommen. Zusammenfassend lässt sich sagen, dass die Entwicklung eines Spiels in Java mit den in dieser Arbeit vorgestellten Mitteln durchaus möglich und auch sinnvoll ist. Eine genaue Spezifikation der Anforderungen an die Per- KAPITEL 6. EVALUIERUNG UND SCHLUSSBEMERKUNGEN 75 formance sollte im Vorfeld jedoch nicht fehlen, da jene ab einer gewissen Komplexität des Spieles sicherlich noch ihre Grenzen hat. 6.3 Ausblick Die vorliegende Version von JSceneViewer3D bietet ein Gründgerüst, um komplexe 3D-Szenen unter Verwendung von Java und Java 3D darzustellen. Es ist möglich, Quake III -Levels zu laden und anzuzeigen. Dabei wird die Geometrie vollständig geladen, Texturen werden angezeigt und Entitäten zum Teil verarbeitet. Um alle Details der Welt darstellen zu können, müssen in weiterer Folge die verbleibenden, bis jetzt nicht ausgelesenen Lumps ausgewertet werden. Dies betrifft vor allem Planes, Models, Brushes, Effects und Lightmaps. Jene Lumps, welche die BSP-Baum Struktur repräsentieren (Nodes, Leafs, Leaffaces, Leafbrushes) müssen nicht beachtet werden. Um die Ergebnisse der bestehenden Applikation weiter zu verbessern, kann versucht werden, wichtige Codeteile zu optimieren. So werden etwa zur Zeit Polygone und Meshes noch mit Triangle-Arrays gerendert und BackfaceCulling wurde deaktiviert, um das Level von beiden Seiten sichtbar zu machen. Eine sinnvolle Erweiterungsmöglichkeit besteht darin, neben Java 3D noch JSR 231/JOGL zur Darstellung einzusetzen. In diesem Fall müssen der Level-Loader angepasst und ein eigener Renderer entworfen werden. Letzterer kann dann gegebenfalls auch den BSP-Baum auswerten und ihn zur Sichtbarkeitsbestimmung verwenden. Auch andere Sichtbarkeitsalgorithmen (Culling) müssen ebenfalls implementiert werden. Mit einem zweiten, direkt auf OpenGL basierenden Renderer können in Folge akkuratere Vergleichstest durchgeführt werden. Weiters bietet sich die Implementierung von Level-Loadern für andere Formate an. Mögliche Spiele, deren Levels geeignet sein könnten, wären Unreal Tournament oder Half-Life. Folgeprojekte dieser Arbeit können auf den hier gewonnenen Erkenntnissen aufbauen, um etwa die Leistungsfähigkeit zukünftiger Java Versionen, wie etwa der Java 2 Standard Edition Version 6.0 (Codename Mustang“) ” zu überprüfen. Auch die weiteren Entwicklungen von Java 3D können dabei miteinbezogen werden. Version 1.5 ist zur Zeit als Beta-Version verfügbar und wird in absehbarer Zeit zuverlässig einsetzbar sein. Die Verwendung von Java in der Spiele-Entwicklung steht zum gegebenen Zeitpunkt noch am Anfang. Zukünftige Entwicklungen werden zeigen, ob diese Konstellation Erfolg haben kann, oder nicht. Anhang A Quake III Level Format Alle in der Folge dargestellten Informationen über das Quake III -LevelFormat sind aus [17] entnommen. Sie wurden für die Auflistung in dieser Arbeit ins Deutsche übersetzt und gegebenfalls durch eigene Ausführungen ergänzt. Die im Quake III -Format verwendeten Bezeichnungen werden im englischen Original verwendet und falls möglich, übersetzt. A.1 Dateistruktur Die Dateistruktur der Quake III BSP-Datei ist ähnlich anderer in der Regel ältererer BSP-Formate (etwa von Quake oder Quake II ). Am Beginn steht ein Header. Dieser enthält Informationen über die verwendete BSP-Version und ein Verzeichnis aller 17 so genannten Lumps. Jeder einzelne Lump enthält einen bestimmten Teil der Level-Informationen. Abbildung A.1 stellt diese Struktur schematisch dar. Abbildung A.1: Der Aufbau einer BSP-Datei. Auf den Header folgen die 17 Lumps in vorgegebener Reihenfolge. 76 ANHANG A. QUAKE III LEVEL FORMAT A.2 77 Datentypen In einer Quake III BSP-Datei kommen vier verschiedene Datentypen zum Einsatz: ubyte: Vorzeichenloses Byte (unsigned byte). Zumeist für die Speicherung von Farbwerten verwendet. int: 4 Byte große Ganzzahl (integer), little-Endian. float: 4 Byte große Fließkommazahl (float), little-Endian. string[n]: Zeichenkette (string) bestehend aus n ASCII Bytes, nicht zwingend null-terminiert. Alle Daten in einer BSP-Datei sind in Blöcken zusammengefasst, die immer aus diesen vier Datentypen bestehen. Blöcke sind in weiterer Folge fett dargestellt, Datentypen werden dicktengleich gekennzeichnet und die Namen von Datentypen oder Blöcken sind kursiv abgebildet. Die Inhalte der Blöcke werden wiederum in eigenen Unterabschnitten erläutert. Die Anzahl der Blöcke in jedem Lump kann durch die Division der Größe eines Blocks in Byte mit der Gesamtlänge des Lumps in Bytes errechnet werden. A.3 Header und Verzeichniseinträge Der Header einer Quake III BSP-Datei steht immer am Anfang und hat immer die gleiche Länge. Er besteht aus folgenden Teilen: header string[4] magic: Die so genannte magische Nummer (magic number). Enthält immer den Wert IBSP“. ” int version: Die Versionsnummer der BSP-Datei. Im Falle von Quake III ist der Wert 0x2e“ (46). ” direntry[17] direntries: Das Verzeichnis mit Lump-Einträgen. Es enthält 17 Elemente. direntry Anhand des Verzeichniseintrages kann die Position und Länge eines jeden Lumps in der BSP-Datei genau ermittelt werden. int offset: Der Offset dieses Lumps in Bytes zum Anfang der BSPDatei. int length: Die Länge des Lumps in Bytes. Sie beträgt immer ein Vielfaches von vier. ANHANG A. QUAKE III LEVEL FORMAT A.4 78 Lumps Jede Quake III BSP-Datei besteht aus genau 17 so genannten Lumps. Sie sind im Lump-Verzeichnis des Headers in der durch die folgenden Abschnitte dargestellten Reihenfolge referenziert. A.4.1 Entities (Entitäten) Der Entitäten Lump beinhaltet Informationen über das Level wie den Namen oder auch die zu verwendende ambiente Beleuchtung. Darüber hinaus spezifiziert der Lump Objekte, die in der Karte platziert werden. Solche Objekte sind etwa Waffen, Rüstungen oder Health Packs (Gegenstände zur Verbesserung der Gesundheit). Genauso jedoch werden die Startpunkte des Spielers angegeben, Lichter platziert und zu ladende Modelle beschrieben. Der Lump enthält genau einen Block, der die Entitäten textuell beschreibt. entities string[l] entities: Die Beschreibung aller Entitäten als Zeichenkette der Länge l. l ist durch die Länge des Lumps festgelegt, die im Lump-Verzeichnis angeführt ist. Details zu den einzelnen Entitäten und deren Parametern können [10] entnommen werden. A.4.2 Textures (Texturen) Dieser Lump enthält die Namen der Texturen, die auf Oberflächen angebracht werden. Die Texturen werden über eindeutige Indices bei Faces, Brushes und Brushsides referenziert. texture string[64] name: Der Name der Textur (ohne Dateierweiterung). int flags: Parameter (flags) für die verwendete Oberfläche. int contents: Parameter (flags) des Inhalts der Textur. A.4.3 Planes (Ebenen) Dieser Lump enthält eine bestimmte Anzahl von Planes, die von Nodes und Brushsides referenziert werden. Um beidseitig sichtbar zu sein, werden immer zwei Ebenen mit jeweils invertiertem Normalvektor benötigt. Dies ist der Fall bei den Planes mit den Ebenen i und i ∧ 1 (XOR-Verbindung mit 1). ANHANG A. QUAKE III LEVEL FORMAT 79 plane float[3] normal: Der Normalvektor der Ebene. float dist: Die Distanz der Ebene zum Ursprung, gemessen entlang des Normalvektors. A.4.4 Nodes (Knoten) Der Knoten-Lump speichert alle Knoten des verwendeten BSP-Baums. Jener wird verwendet, um das Level räumlich in konvexe Regionen aufzuteilen. Diese werden als Leafs bezeichnet. Der erste Knoten im Lump ist der Wurzelknoten (Root-Node). node int plane: Der Index der zugewiesenen Ebene. int[2] children: Die Indices der Kinder dieses Knotens. Ist der Wert negativ, handelt es sich um den Index eines Leafs: -(leaf + 1). int[3] mins: Die minimalen Koordinaten der Bounding-Box. int[3] maxs: Die maximalen Koordinaten der Bounding-Box. A.4.5 Leafs (Blätter) Dieser Lump enthält alle Blätter des BSP-Baums. Diese sind die Endstücke, die sich nicht mehr weiter verzweigen und repräsentieren eine konvexe Region des Raums. Jedes Leaf enthält einen so genannten Cluster-Index, der die Sichtbarkeit anderer Leafs von diesem aus gesehen bestimmt. Weiters eine Liste von darzustellenden Faces und eine Liste von Brushes, die für die Kollisionserkennung herangezogen werden. Ist der Cluster-Index negativ, befindet sich das Leaf außerhalb des Levels oder ist ungültig. leaf int cluster : Der Cluster Index der Sichtbarkeitsdaten. int area: Der Index eines Portals, welches für die Sichtbarkeitsberechnung bei Türen benötigt wird. int[3] mins: Die minimalen Koordinaten der Bounding-Box. int[3] maxs: Die maximalen Koordinaten der Bounding-Box. int leafface: Das erste Leafface dieses Leafs. int n leaffaces: Die Anzahl der Leaffaces dieses Leafs. int leafbrush: Der Erste Leafbrush dieses Leafs. int n leafbrushes: Die Anzahl der Leafbrushes dieses Leafs. ANHANG A. QUAKE III LEVEL FORMAT A.4.6 80 Leaffaces Dieser Lump speichert eine liste von Face-Indices, wobei für jedes Leaf eine eigene Liste (repräsentiert durch einen leafface-Block) vorhanden ist. leafface int face: Der Index des zugehörigen Faces. A.4.7 Leafbrushes Dieser Lump speichert eine Liste von Brush-Indices, wobei für jeden Brush eine eigene Liste (repräsentiert durch einen leafbrush-Block) vorhanden ist. leafbrush int brush: Der Index des zugehörigen Brushs. A.4.8 Models (Modelle) Der Models-Lump beschreibt rigide Teile der Level-Geometrie. Das erste Modell repräsentiert dabei den Unterbau des Levels, die restlichen beschreiben bewegliche Teile wie Türen, Plattformen und Knöpfe. Jedes Modell besteht aus einer Liste von Faces und Brushes, welche vor allem für die beweglichen Teile von Bedeutung sind, die ja – anders als der Unterbau – keinen BSP-Baum besitzen. model float[3] mins: Die minimalen Koordinaten der Bounding-Box. float[3] maxs: Die maximalen Koordinaten der Bounding-Box. int face: Das erste Face dieses Modells. int n faces: Die Anzahl der Faces dieses Modells. int brush: Der erste Brush dieses Modells. int n brushes: Die Anzahl der Brushes dieses Modells. A.4.9 Brushes Im Brush-Lump wird eine Liste von Brushes gespeichert, welche für die Kollisionserkennung benötigt werden. Als Brush wird ein konvexer Polyeder bezeichnet. ANHANG A. QUAKE III LEVEL FORMAT 81 brush int brushside: Die erste Brushside dieses Brushs. int n brushsides: Die Anzahl an Brushsides in diesem Brush. int texture: Der Index der verwendeten Textur. A.4.10 Brushsides Als Brushside wird eine Seitenfläche der Bounding-Box bezeichnet, die einen Brush umgibt. Der Brushside-Lump enthält die Beschreibungen dieser Flächen. brushside int plane: Der Index der verwendeten Fläche. int texture: Der Index der verwendeten Textur. A.4.11 Vertexes (Vertices) Der Vertex-Lump enthält Vertexlisten, die zur Beschreibung von Faces verwendet werden. vertex float[3] position: Die Koordinaten (Position) des Verticis. float[2][2] texcoord: Die Texturkoordinaten des Verticis. Die Koor- dinaten an Stelle 0 des Arrays sind die Oberflächen-Texturkoordinaten, an Stelle 1 befinden sich die Lightmap-Daten. float[3] normal: Der Normalvektor des Verticis. ubyte[4] color : Die Farbe des Verticis, gespeichert im Format RGBA (rot, grün, blau, alpha). A.4.12 Meshverts Der Meshvert-Lump enthält eine Liste von Offsets, die benötigt werden, um Dreiecke zu beschreiben. Eine andere Bezeichnung lautet Vertex-Indices. meshvert int offset: Der Vertex-Index Offset, relativ zum ersten Vertex im dazugehörigen Face. ANHANG A. QUAKE III LEVEL FORMAT A.4.13 82 Effects (Effekte) Der Effekt-Lump enthält Referenzen zu volumetrischen Shadern wie etwa Nebel. Effekte können immer auf eine Gruppe von Faces angewandt werden. effect string[64] name: Der Name des verwendeten Shaders. int brush: Der Index des Brushs, von dem dieser Effekt generiert wurde. int unknown: Zusätzlicher Parameter, der bis auf wenige Ausnahmen immer den Wert 5 enthält. A.4.14 Faces Der Face-Lump enthält alle Informationen, um die Geometrie des Levels darzustellen. In Quake III kommen vier verschiedene Typen von Faces zum Einsatz: Polygone, Bézier Patches, Meshes und Billboards. Handelt es sich bei dem Face um ein Polygon, ist durch vertex und n vertexes die Liste an Vertices festgelegt, die dieses Polygon bilden. Diese Liste ist immer vollständig und enthält in bestimmten Fällen sogar noch einen zusätzlichen Vertex im Zentrum des Polygons. Die Werte meshvert und n meshverts beinhalten die Indizes, die benötigt werden, um das Dreieck in der richtigen Reihenfolge zu erstellen. Ist das aktuelle Face ein Patch, so beinhalten vertex und n vertexes eine Liste von so genannten Kontroll-Vertices. Die Anzahl und Ausdehnung wird durch size vorgegeben. Informationen über die Verarbeitung und Darstellung dieser Patches sind in [1] und [16] vorhanden. Meshes können äquivalent zu Polygonen repräsentiert werden. Für Billboards beschreibt vertex die Position des Billboards. Verwendung finden diese vor allem bei Lichtreflex-Effekten (Flare). face int texture: Der Index der Textur, die für dieses Face verwendet werden soll. int effects: Ein Index auf den Effekt-Lump, falls für dieses Face ein Effekt zum Einsatz kommt. Andernfalls ist der Index -1. int type: Spezifiziert, ob es sich bei dem Face um ein Mesh, ein Polygon, einen Bézier-Patch oder ein Billboard handelt. int vertex : Der Index des ersten Verticis im Face. int n vertexes: Die Anzahl der Vertices im Face. ANHANG A. QUAKE III LEVEL FORMAT 83 int meshvert: Der Index des ersten Mesh-Verticis im Face. int n mehsverts: Die Anzahl der Mesh-Vertices (Indizes) im Face. int lm index : Der Index der Lightmap. int[2] lm start: Spezifiziert die linke obere Ecke der Lightmap für dieses Face in der gesamten Lightmap-Textur. int[2] lm size: Beziffert die Ausdehnung der Lightmap-Textur für dieses Face innerhalb der gesamten Lightmap. float[3] lm origin: Die Weltkoordinaten der Lightmap. float[2][3] lm vecs: Die Textur-Vektoren für diese Lightmap. float[3] normal: Die Oberflächen-Normale für dieses Face. int[2] size: Die Größe eines Bézier-Patches (falls es sich bei dem Face um einen solchen handelt). Die mit lm beginnenden Werte enthalten Informationen zur Darstellung von Lightmaps. Besitzt ein Face eine Lightmap, ist lm index positiv. Dieser Index verweist auf den Lightmap-Lump, der eine große Lightmap enthält. Über lm start und lm size kann das benötigte Sub-Bild eingegrenzt werden. Bei Polygonen können lm origin und lm vecs verwendet werden, um die Position des Polygons im Weltkoordinatensystem zu berechnen. Diese Position kann in weiterer Folge verwendet werden, um dynamische Beleuchtung für dieses Face zu berechnen. Es ist zu beachten, dass keine der mit lm beginnenden Werte verwendet werden, um Textur-Koordinaten der Lightmaps zu berechnen. Diese sind vielmehr bereits mit den Vertices mitgespeichert und ersparen somit Berechnungszeiten. A.4.15 Lightmaps Dieser Lump speichert die Lightmap-Texturen, die angewandt werden können, um Oberflächen realistischer zu beleuchten. Jede Lightmap ist 128 × 128 Pixel groß und besteht aus RGB Farbwerten. lightmap ubyte[128][128][3] map: Die RGB Farbwerte der Lightmap. A.4.16 Lightvols (Beleuchtungsdaten) Der Lightvols-Lump enthält ein einheitliches Gitternetz mit Beleuchtungsinformationen um Objekte zu beleuchten, die nicht in der Karte enthalten sind. ANHANG A. QUAKE III LEVEL FORMAT 84 lightvol ubyte[3] ambient: Der ambiente Anteil der Beleuchtung. ubyte[3] directional: Der gerichtete Anteil der Beleuchtung. ubyte[2] dir : Die Richtung des Lichts, gegeben in Kugelkoordinaten. Der Wert an der Stelle 0 ist der Polarwinkel φ, der Wert an Stelle 1 der Azimutwinkel θ. A.4.17 Visdata (Sichtbarkeitsinformationen) Im Visdata-Lump sind Vektoren enthalten, die eine Anzahl so genannter Cluster-zu-Cluster Sichtbarkeitsinformationen beinhalten. Jede BSP-Datei enthält genau einen visdata-Block. visdata int n vecs: Die Anzahl der verfügbaren Vektoren. int sz vecs: Die Größe jedes Vektors in Bytes. ubyte[n_vecs ∗ sz_vecs] vecs: Die Sichtbarkeitsdaten. Dabei wird ein Bit pro Cluster pro Vektor zugeordnet. Anhang B Inhalt der DVD File System: ISO9660 + Joliet Mode: Single-Session DVD B.1 Diplomarbeit Pfad: / da dm04007.dvi . . . . da dm04007.pdf . . . . da dm04007.ps . . . . . B.2 Literatur Pfad: /literatur/ essential facts.pdf . . . java myths.pdf . . . . . java productivity.pdf . . java technology.pdf . . java vms.pdf . . . . . . java vs c++.pdf . . . . performance tests.pdf . q3radiant manual.pdf . q3 specs.pdf . . . . . . rendering patches.pdf . rendering q3 maps.pdf the java language.pdf . Diplomarbeit (DVI-Datei) Diplomarbeit (PDF-Datei) Diplomarbeit (PostScript-Datei) Dokument Dokument Dokument Dokument Dokument Dokument Dokument Dokument Dokument Dokument Dokument Dokument 85 zu zu zu zu zu zu zu zu zu zu zu zu [3] [21] [24] [12] [11] [13] [14] [10] [17] [1] [16] [8] ANHANG B. INHALT DER DVD 86 B.3 Performance-Daten Pfad: /daten/ logs/ . . . . . . . . . . . videos/ . . . . . . . . . B.3.1 Pfad: Log-Dateien der Testläufe Vidoes der Testläufe Log-Dateien /daten/logs/ cpm4a 640 cp/ . . cpm4a 640 ncp/ . cpm4a 1280 cp/ . cpm4a 1280 ncp/ cpm13 640 cp/ . . cpm13 640 ncp/ . cpm13 1280 cp/ . cpm13 1280 ncp/ cpm19 640 cp/ . . cpm19 640 ncp/ . cpm19 1280 cp/ . cpm19 1280 ncp/ cpm24 640 cp/ . . cpm24 640 ncp/ . cpm24 1280 cp/ . cpm24 1280 ncp/ cpm25 640 cp/ . . cpm25 640 ncp/ . cpm25 1280 cp/ . cpm25 1280 ncp/ q3 cpm4a 640/ . . q3 cpm4a 1280/ . q3 cpm13 640/ . . q3 cpm13 1280/ . q3 cpm19 640/ . . q3 cpm19 1280/ . q3 cpm24 640/ . . q3 cpm24 1280/ . q3 cpm25 640/ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte Karte cpm4a, cpm4a, cpm4a, cpm4a, cpm13, cpm13, cpm13, cpm13, cpm19, cpm19, cpm19, cpm19, cpm24, cpm24, cpm24, cpm24, cpm25, cpm25, cpm25, cpm25, cpm4a, cpm4a, cpm13, cpm13, cpm19, cpm19, cpm24, cpm24, cpm25, 640 × 480, kompiliert 640 × 480, nicht kompiliert 1280 × 1024, kompiliert 1280 × 1024, nicht kompiliert 640 × 480, kompiliert 640 × 480, nicht kompiliert 1280 × 1024, kompiliert 1280 × 1024, nicht kompiliert 640 × 480, kompiliert 640 × 480, nicht kompiliert 1280 × 1024, kompiliert 1280 × 1024, nicht kompiliert 640 × 480, kompiliert 640 × 480, nicht kompiliert 1280 × 1024, kompiliert 1280 × 1024, nicht kompiliert 640 × 480, kompiliert 640 × 480, nicht kompiliert 1280 × 1024, kompiliert 1280 × 1024, nicht kompiliert 640 × 480, Quake III 1280 × 1024, Quake III 640 × 480, Quake III 1280 × 1024, Quake III 640 × 480, Quake III 1280 × 1024, Quake III 640 × 480, Quake III 1280 × 1024, Quake III 640 × 480, Quake III ANHANG B. INHALT DER DVD q3 cpm25 1280/ . . . . B.3.2 Pfad: Karte cpm25, 1280 × 1024, Quake III Videos /daten/videos/ jsv3d/ . . . . . . . . . . quake3/ . . . . . . . . . B.4 Quellcode Pfad: /quellcode/ bin/ . . . . . . . . . dep/ . . . . . . . . . doc/ . . . . . . . . . lib/ . . . . . . . . . log/ . . . . . . . . . src/ . . . . . . . . . .classpath . . . . . . .project . . . . . . . JSceneViewer3D.bat . . . . . . . . . . . . . . . . . . Videos der JSceneViewer3D Testläufe Videos der Quake III Testläufe Klassen-Dateien Benötigte Komponenten zur Ausführung JavaDoc Dokumentation Externe Bibliotheken Order für Log-Dateien Quellcode-Dateien eclipse Klassenpfad-Datei eclipse Projekt-Datei Batch-Datei zur Ausführung 87 Literaturverzeichnis [1] Bentley, C.: Rendering Cubic Bezier Patches. URL, http://web.cs. wpi.edu/˜matt/courses/cs563/talks/surface/bez surf.html, 1995. Kopie auf CD-ROM. [2] Davison, A.: Killer Game Programming in Java. O’Reilly Media, Sebastopol, Erste Aufl., 2005. [3] Entertainment Software Association: Essential Facts about the Computer and Video Game Industry. URL, http://theesa.com/files/ 2005EssentialFacts.pdf, 2005. Kopie auf CD-ROM. [4] Fuchs, H., Z. M. Kedem und B. Naylor: Predetermining visibility priority in 3-D scenes (Preliminary Report). In: SIGGRAPH ’79: Proceedings of the 6th annual conference on Computer graphics and interactive techniques, S. 175–181, New York, NY, USA, 1979. ACM Press. [5] Fuchs, H., Z. M. Kedem und B. F. Naylor: On visible surface generation by a priori tree structures. In: SIGGRAPH ’80: Proceedings of the 7th annual conference on Computer graphics and interactive techniques, S. 124–133, New York, NY, USA, 1980. ACM Press. [6] Gamma, E., R. Helm, R. Johnson und J. Vlissides: Entwurfsmuster . Addison Wesley Verlag, München, Erste Aufl., 2004. [7] Gülcü, C.: The Complete log4j Manual. COS.ch, 2003. [8] Gosling, J.: The Java Language: An Overview . URL, http://java.sun. com/docs/overviews/java/java-overview-1.html, 1996. Kopie auf CDROM. [9] Huizinga, J.: Homo Ludens. Vom Ursprung der Kultur im Spiel. Rowohlt, 19. Aufl., Jänner 1994. [10] Jaquays, P.: Q3Radiant Editor Manual. URL, http://www.qeradiant. com/manual/Q3Rad Manual/index.htm, 2000. Kopie auf CD-ROM. 88 LITERATURVERZEICHNIS 89 [11] java-virtual-machine.net: Java virtual machines, Java development kits and Java runtime environments. URL, http://java-virtual-machine. net/other.html, 2006. Kopie auf CD-ROM. [12] Levin, R.: Java Technology Comes of Age. URL, http://java.sun.com/ features/1999/05/birthday.html, Mai 1999. Kopie auf CD-ROM. [13] Lewis, J. P. und U. Neumann: Performance of Java versus C++. URL, http://www.idiom.com/˜zilla/Computer/javaCbenchmark. html, Januar 2003. Kopie auf CD-ROM. [14] Mangione, C.: Performance tests show Java as fast as C++. URL, http://www.javaworld.com/jw-02-1998/jw-02-jperf.html, 1998. Kopie auf CD-ROM. [15] Marner, J.: Evaluating Java for Game Development. Techn. Ber. 01-11-11, Department of Computer Science, University of Copenhagen, Denmark, Kopenhagen, Dänemark, März 2002. [16] McGuire, M.: Rendering Quake 3 Maps. URL, http://graphics.cs. brown.edu/games/quake/quake3.html, Juli 2003. Kopie auf CD-ROM. [17] Proudfood, K.: Unofficial Quake 3 Map Specs. URL, http://graphics. stanford.edu/˜kekoa/q3/, Januar 2000. Kopie auf CD-ROM. [18] Rollings, A. und E. Adams: Andrew Rollings and Ernest Adams on Game Design. New Riders, Erste Aufl., 2003. [19] Rollings, A. und D. Morris: Game Architecture and Design. The Coriolis Group, Scottsdale, Erste Aufl., 2000. [20] Scherfgen, D.: 3D-Spieleprogrammierung - Modernes Game Design mit DirectX 9 und C++. Carl Hanser Verlag, Wien, Erste Aufl., 2003. [21] Twilleager, D.: Java Game Development Myths. URL, http:// weblogs.java.net/blog/yensid/archive/2005/02/java game devel.html, Februar 2005. Kopie auf CD-ROM. [22] Tyma, P.: Why are we using Java again? . Commun. ACM, 41(6):38– 42, 1998. [23] Ullenboom, C.: Java ist auch eine Insel. Galileo Press GmbH, Bonn, Vierte Aufl., 2005. [24] Wells, R.: Java offers Increased Productivity. URL, http://www. wellscs.com/robert/java/productivity.htm, Mai 1999. Kopie auf CDROM. [25] Zerbst, S., O. Düvel und E. Anderson: 3D-Spieleprogrammierung. Markt+Technik Verlag, München, Erste Aufl., 2004. Messbox zur Druckkontrolle — Druckgröße kontrollieren! — Breite = 100 mm Höhe = 50 mm — Diese Seite nach dem Druck entfernen! — 90