3D Visualisierung technischer Daten in Webapplikationen
Transcription
3D Visualisierung technischer Daten in Webapplikationen
Fachbereich Elektrotechnik und Informatik; Bochum University of Applied Sciences 3D Visualisierung technischer Daten in Webapplikationen Bachelorthesis in Studiengang Mechatronik & Informationstechnologie Dirk Cziesla ∗ Version vom 27. März 2014 Betreut von Prof. Dr. rer. Jörg Frochte (Hochschule Bochum) Dipl.-Ing. Jörg Gudat (Gudat Consulting) ∗ [email protected] Copyright und Bildquellen Text: Copyright (c) 2013 Dirk Cziesla Bilder und Skizzen, soweit nicht anders angegeben: Copyright (c) 2013 Dirk Cziesla Lizenz: CC-by-nc-nd (Version 3.0) http://creativecommons.org/licenses/by-nc-nd/3.0/de/legalcode Den rechtsverbindlichen Lizenzvertrag finden Sie unter dem oben angegebenen Link. Es folgt eine vereinfachte Zusammenfassung des Vertrages in allgemeinverständlicher Sprache ohne juristische Wirkung. Es ist Ihnen gestattet das Skript zu vervielfältigen, zu verbreiten und öffentlich zugänglich zu machen sofern Sie folgende Bedingungen einhalten: • Namensnennung: Sie müssen die Urheberschaft ausreichend deutlich benennen, einen Link zur Lizenz beifügen. Diese Angaben dürfen in jeder angemessenen Art und Weise gemacht werden, allerdings nicht so, dass der Eindruck entsteht, der Lizenzgeber unterstütze gerade Sie oder Ihre Nutzung des Werks besonders. • Keine kommerzielle Nutzung: Sie dürfen das Material nicht für kommerzielle Zwecke nutzen. • Keine Bearbeitung: Wenn Sie das Material remixen, verändern oder darauf anderweitig direkt aufbauen, dürfen Sie die bearbeitete Fassung des Materials nicht verbreiten. • Lizenzangabe: Sie müssen Anderen alle Lizenzbedingungen mitteilen, die für dieses Werk gelten. Am einfachsten ist es, wenn Sie dazu einen Link auf den Lizenzvertrag (siehe oben) einbinden. Abweichende Lizenzen für einzelnen Bilder und Skizzen werden ggf. separat angegeben. Es wurde jedoch darauf geachtet, dass keine dieser Lizenzen die Möglichkeiten der Rechte im obigen Sinne einschränkt. Codebeispiele dürfen unter der BSD-3-Clause http://opensource.org/licenses/BSD-3-Clause verwendet und weitergegeben werden. Abstract Diese Bachelor-Arbeit beschäftigt sich mit der 3D Visualisierung technischer Daten in Webapplikationen. Ziel ist es dabei prototypisch darzustellen wie performant eine Visualisierung von 3D-Daten im Browser gelingen könnte. Um dieses Ziel zu erreichen, werden zuerst die verschiedenen Möglichkeiten Software in das Web zu übertragen vorgestellt. Als Technologien für die Umsetzung wird sich für WebSockets und WebGL entschieden. Mit diesen beiden Technologien wird, basierend auf einer bereits existierenden Anwendung, ein Prototyp als Webanwendung entwickelt. Eine besondere Herausforderung stellt dabei das Übertragen der sehr großen Gitter-Daten dar. Speziell für diesen Anwendungsfall wird daher ein Datenformat entwickelt, bei dem die Gitter-Daten möglichst kompakt übertragen werden. Eine weitere Herausforderung ist das Transparent Schalten von einzelnen Punkten in einem 3D-Gitter. Diese Funktion wird nicht nativ unterstützt und muss daher mithilfe von einem eigenen Shader umgesetzt werden. Abschließend wird die Umsetzung der sonstigen GUI-Elemente und möglichen Techniken zur Verbesserung der Datenübertragung vorgestellt. Inhaltsverzeichnis 1 Einleitung 4 2 Grundlagen und Technologien 7 2.1 Möglichkeiten zur Visualisierung von Daten im Browser . . . . . . . . . . . 7 2.1.1 SVG . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.1.2 Application-Streaming und Anwendungsserver . . . . . . . . . . . . 10 2.1.3 WebGL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.1.4 Zwischenfazit Visualisierung . . . . . . . . . . . . . . . . . . . . . . 14 2.2 HTML5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.3 JSON . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.4 Datenübertragung zwischen Server und Browser . . . . . . . . . . . . . . . 15 2.4.1 GET- und POST-Anfragen . . . . . . . . . . . . . . . . . . . . . . 16 2.4.2 (Long) Polling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 2.4.3 WebSocket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.4.4 Zwischenfazit Datenübertragung . . . . . . . . . . . . . . . . . . . . 21 Echtzeit- und Performance-Anforderungen für Webapplikationen . . . . . . 22 2.5 3 Anforderungsanalyse für den Prototypen 3.1 3.2 Anforderungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 3.1.1 Bedienungsanforderungen . . . . . . . . . . . . . . . . . . . . . . . 24 3.1.2 Datenmenge und Performance . . . . . . . . . . . . . . . . . . . . . 28 3.1.3 Sicherheit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 Abgeleitete Software- und Systemarchitektur . . . . . . . . . . . . . . . . . 32 4 Technische Umsetzung 4.1 4.2 2 24 35 WebGL – Detailliertere Technologiebetrachung . . . . . . . . . . . . . . . . 35 4.1.1 Gemeinsamkeiten und Unterschiede zu OpenGL . . . . . . . . . . . 35 4.1.2 Interaktion mit JavaSkript . . . . . . . . . . . . . . . . . . . . . . . 35 3D Visualisierung der Simulationsdaten . . . . . . . . . . . . . . . . . . . . 36 4.3 4.4 4.2.1 Verwendete Bibliotheken . . . . . . . . . . . . . . . . . 4.2.2 Anbinden des Backends . . . . . . . . . . . . . . . . . 4.2.3 Das Übertragunsformat . . . . . . . . . . . . . . . . . 4.2.4 RAW-Format . . . . . . . . . . . . . . . . . . . . . . . 4.2.5 Perspektivische und Orthographische Ansichten . . . . 4.2.6 Reines WebGL vs Three.js . . . . . . . . . . . . . . . . 4.2.7 MeshBasicMaterial, ShaderMaterial und Transparenz . 4.2.8 Auswahl von Elementen . . . . . . . . . . . . . . . . . 4.2.9 Ausblenden von Elementen . . . . . . . . . . . . . . . . 4.2.10 Legende . . . . . . . . . . . . . . . . . . . . . . . . . . Umsetzung der sonstigen GUI-Elemente . . . . . . . . . . . . (Mögliche) Techniken zur Verbesserung der Datenübertragung 4.4.1 Coarsening . . . . . . . . . . . . . . . . . . . . . . . . 4.4.2 Trennung von Gitter- und Lösungsdaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 40 40 43 45 46 53 59 65 69 74 76 76 76 5 Fazit und Ausblick 78 Literatur- und Quellenverzeichnis 88 Eidesstattliche Erklärung 89 3 1 Einleitung Die Visualisierung von technischen Daten findet sich in vielen Bereichen wieder. Ob in der Biologie, Chemie, Physik oder Medizin, fast immer gibt es Anwendungsfälle, um mit einer Visualisierung die Lösung eines Problems etwas besser darzustellen. Einige Beispiele dafür sind die Visualisierung von Gelände in 3D, Visualisierung von Daten einer Simulation (Tornadosimulator, Erdbebensimulator, Finite Elemente Simulation) oder auch die Visualisierung der Daten aus einem MRT (Magnetresonanztomographie) Scan. Ein Beispiel aus dem Bereich der Finite Elemente Simulation ist das Projekt SimCloud. Dieses kombiniert Aspekte von Cloud Computing, maschinellem Lernen und Simulation. Hauptsächlich wird sich jedoch mit der Finite Elemente Simulation (FES) auf verteilten Systemen beschäftigt, wobei die Probleme mit der Finite-Elemente-Methode (FEM) gelöst werden (für weitere Informationen zur FEM siehe: [FROC2005, S. 25ff]). Die Probleme bewegen sich hauptsächlich im Bereich CAE (Computer Aided Engineering) und CAD (Computer Aided Design). Zu Beginn dieser Bachelor-Arbeit besteht die in dem Projekt entwickelte Software aus zwei Komponenten. Zum einen gibt es das Host-Software-Backend (FEMCore) und zum anderen die grafische Benutzeroberfläche (GUI) zur Ansteuerung bzw. Bedienung des FEMCore, welche als wxWidgetGUI bezeichnet wird. Sowohl FEMCore als auch wxWidgetGUI sind aktuell nur auf dem Betriebssystem Linux lauffähig. Ziel dieser Bachelor-Arbeit ist es, anhand des oben erwähnten Problemkomplexes prototypisch darzustellen, wie performant eine Visualisierung von 3D-Daten im Browser gelingen könnte. In Kapitel 2 werden die Technologien vorgestellt, welche für die Portierung der Funktionalitäten verwendet werden könnten. Dabei werden zuerst die verschiedenen Möglichkeiten zur Visualisierung von Daten im Browser miteinander verglichen. Bei diesem Vergleich stellt sich heraus, dass WebGL die sinnvollste der vorgestellten Möglichkeiten ist und daher bei 4 der 3D-Visualisierung zur Verwendung kommt. Damit ist festgelegt, dass die Applikation als Webapplikation umgesetzt wird. Diese wird als WebGUI bezeichnet. Anschließend werden die zwei Technologien HTML5 und JSON erklärt, die die Grundlage für diese Webapplikation bilden. Darauf basierend werden die Möglichkeiten zur Kommunikation zwischen Server und Browser verglichen. Dabei stellt sich heraus, dass WebSocket aufgrund diverser Vorteile für die Umsetzung Verwendung finden soll. Abschließend werden die Echtzeit- und Performanceanforderungen für Webapplikationen in Kapitel 2.5 ermittelt. Dabei zeigt sich, dass es diverse Nachteile bei der Verwendung einer Web- anstelle einer Desktop-Applikation gibt, vor allem was die Anbindung an den FEMCore angeht. Kapitel 3 behandelt die Anforderungsanalyse für die Webapplikation. Im ersten Abschnitt werden die Anforderungen aus dem Lastenheft für die wxWidgetGUI abgeleitet, welche sich in Bedienungsanforderungen“, Datenmenge und Performance“ und Sicherheit“ un” ” ” terteilen lassen. In Kapitel 3.1.2 wird dabei festgestellt, dass (je nach Größe des Problems) DSL6000 bzw. VDSL 25 als minimale Voraussetzung gilt. Anschließend werden in Kapitel 3.1.3 die verschiedenen Möglichkeiten zur Erhöhung der Verbindungssicherheit vorgestellt. Zum Schluss wird für die zuvor spezifizierten Anforderungen die Software- und Systemarchitektur generiert. Dabei wird das FEMInterface definiert, welches die Verbindung zwischen WebGUI und FEMCore darstellt und damit die Herausforderung der Kommunikation zwischen Browser und C++-Backend beseitigt. Die Umsetzung der Software- und Systemarchitektur wird in Kapitel 4 durchgeführt. Hierzu wird zuerst in Kapitel 4.1 die Technologie WebGL weiter vertieft. Dabei werden die Unterschiede zu OpenGL erklärt, welche jedoch keine Auswirkung auf das Projekt haben. Anschließend wird in Kapitel 4.2 die Visualisierung der Simulationsdaten umgesetzt. Da eine eigene Implementierung eines WebSocket-Servers und eines JSON-(De)Serialisierers mit einem zu großen Arbeits- und Zeitaufwand verbunden ist, werden in Kapitel 4.2.1 Bibliotheken mit der benötigten Funktionalität ermittelt. Mithilfe dieser Bibliotheken wird der FEMCore mit der WebGUI über das FEMInterface verbunden. Als Übertragungsformat wird dabei JSON verwendet, welches in Kapitel 4.2.3 definiert wird. Dies führt jedoch zu einer weiteren Herausforderung, da aufgrund der großen Menge an 5 Overhead bei JSON ein weiteres Übertragungsformat für besonders große Dateien (GitterDateien) benötigt wird. Dieses wird in Kapitel 4.2.4 definiert und als RAW-Format bezeichnet. Mit der Erklärung der Implementierung des WebGL-Teils wird in Kapitel 4.2.5 begonnen. Dafür werden zuerst die perspektivische und die orthographische Ansicht miteinander verglichen, was darin resultiert, dass die perspektivische Ansicht hier Verwendung findet. Anschließend wird ein Vergleich zwischen reinem WebGL und der Bibliothek Three.js aufgestellt. Aufgrund der besseren Wartbarkeit und auch der besseren Unterstützung für mehrere Entwickler wird Three.js bei der 3D-Visualisierung vorgezogen. Basierend auf den Anforderungen an das Auswählen und Ausblenden von Elementen aus Kapitel 3 wird im Kapitel 4.2.7 vorgestellt, wie Transparenz für einzelne Punkte in Three.js möglich ist. Dies stellt eine Herausforderung dar, da es nicht ohne weiteres möglich ist, die Transparenz für einzelne Punkte in Three.js zu verändern. Kapitel 4.2.8 behandelt daraufhin das Auswählen von Elementen und Kapitel 4.2.9 das Ausblenden von Elementen. Abschließend werden in Kapitel 4.2.10 die verschiedenen Möglichkeiten zur Umsetzung einer Legende erläutert. In Kapitel 4.3 wird die Umsetzung der sonstigen GUI-Elemente vorgestellt. Das Entwickeln von eigenen GUI-Elementen ist mit viel Arbeits- und Zeitaufwand verbunden, daher wird auch hier eine Bibliothek mit den benötigten Funktionalitäten ermittelt. Im letzten Kapitel 4.4 werden verschiedene mögliche Techniken zur Verbesserung der Datenübertragung vorgestellt. Abgeschlossen wird diese Bachelor-Arbeit mit einem Fazit und Ausblick auf die weitere Arbeit in Kapitel 5. 6 2 Grundlagen und Technologien In diesem Kapitel werden die für den Prototypen ausschlaggebenden Technologien und deren Grundlagen erläutert. Dabei wird zuerst auf die verschiedenen Möglichkeiten zur Visualisierung von technischen Daten im Browser eingegangen. Anschließend werden die für die Realisierung des Prototypen als Webapplikation relevanten Technologien HTML5 und JSON vorgestellt, worauf ein Vergleich der Möglichkeiten zur Datenübertragung zwischen Server und Browser folgt. Abschließend werden die Echtzeit- und PerformanceAnforderungen für Webapplikationen ermittelt. 2.1 Möglichkeiten zur Visualisierung von Daten im Browser Dieser Abschnitt dient dazu die verschiedenen Technologien näher zu erläutern, die zur Visualisierung von technischen Daten im Webbrowser verwendet werden können. Dabei stellt Application-Streaming eine Ausnahme dar, da diese Technologie nicht ausschließlich im Webbrowser ausgeführt wird. 2.1.1 SVG SVG ist die erste Technologie, die für die Visualisierung der 3D-Daten im Browser in Frage kommt. SVG“ steht für Scalable Vector Graphics (engl. für skalierbare Vektorgrafik“). ” ” Hierbei handelt es sich nicht um ein Rastergrafikformat wie GIF, JPEG oder PNG, sondern um eine Vektorgrafik. Bei diesen werden nicht die einzelnen Pixel in einer Matrix abgespeichert, sondern die Beschreibung der Schritte, die erforderlich sind, um die gewünschte Grafik zu zeichnen. Das Format ist somit auflösungsunabhängig und daher skalierbar. SVGGrafiken werden in allen aktuellen Browsern unterstützt. Tabelle 2.1 auf Seite 8 zeigt die verschiedenen Browser jeweils mit der Version, seit der SVG unterstützt wird. Zusätzlich wird die aktuelle Version als Referenzwert angegeben. 7 Tabelle 2.1: SVG Unterstützung, Quelle: [DEVE2014a] Browser Version Aktuelle Version Internet Explorer 9.0 11.0 3.0 27.0 Mozilla Firefox Google Chrome 4.0 32.0 3.2 7.0 Apple Safari 9.0 19.0 Opera Der Aufbau einer sehr einfachen SVG-Datei ist in Listing 2.1 dargestellt. Listing 2.1: Aufbau einer einfachen SVG Datei [FLAN2012, S. 666f] 1 <!−− E i n e SVG−F i g u r b e g i n n e n und unserem Namensraum d e k l a r i e r e n −−> 2 <s v g x m l n s=” h t t p : //www. w3 . o r g /2000/ s v g ” 3 <!−− Das K o o r d i n a t e n s y s t e m f ü r d i e F i g u r . −−> 4 viewBox=” 0 0 1000 1000 ”> 5 <!−− E i n i g e D e f i n i t i o n e n e i n r i c h t e n , d i e w i r n u t z e n werden . −−> 6 <d e f s> 7 <!−− E i n F a r b g r a d i e n t names ” f a d e ” . −−> 8 < l i n e a r G r a d i e n t i d=” f a d e ”> 9 10 <s t o p o f f s e t=”0%” s t o p −c o l o r=”#008” /> <s t o p o f f s e t=”100%” s t o p −c o l o r=”#c c f ” /> 11 </ l i n e a r G r a d i e n t> 12 </ d e f s> 13 <!−− E i n R e c h t e c k m i t einem d i c k e n s c h w a r z e n Rand z e i c h n e n und m i t dem G r a d i e n t e n f ü l l e n . −−> 14 15 < r e c t x=” 100 ” y=” 200 ” w i d t h=” 800 ” h e i g h t=” 600 ” s t r o k e=” b l a c k ” 16 s t r o k e −w i d t h=” 25 ” f i l l =” u r l (# f a d e ) ” /> 17 </ s v g> Bild 2.1 auf Seite 9 zeigt die im Browser dargestellte SVG-Datei (vgl. [FLAN2012, S666ff]). Der Vorteil von SVG im Vergleich zu Pixelgrafiken ist, dass man diese sehr einfach per JavaScript verändern kann. JavaScript ist eine Skriptsprache, die die dynamische interaktive Modifikation von Webseiten im Browser ermöglicht (weitere Informationen [KERS2005, S. 855ff]). Beispielhaft soll dazu im soeben generierten Bild 2.1 dynamisch der Farbverlauf geändert werden. Als erstes muss dem Element, das verändert werden soll, eine id zugewiesen werden. Dafür wurde Zeile 9 aus Listing 2.1 wie folgt modifiziert: 8 Bild 2.1: SVG Grafik aus Listing 2.1 [FLAN2012, S. 667] 1 <s t o p i d =” s t a r t o f f s e t ” o f f s e t =”0%” s t o p −c o l o r =”#008” /> Es wurde id="startoffset" hinzugefügt. Jetzt kann auf das Element per JavaScript zugegriffen werden und die Farbe beispielsweise von Blau (#008) auf Grün (#00FF00) geändert werden. 1 v a r s t a r t O f f s e t = document . g e t E l e m e n t B y I d ( ” s t a r t o f f s e t ” ) ; 2 // <s t o p i d =” s t a r t o f f s e t ” o f f s e t =”0%” s t o p −c o l o r =”#008” /> 3 s t a r t O f f s e t . s e t A t t r i b u t e ( ” s t o p −c o l o r ” , ”#00FF00 ” ) ; 4 // <s t o p i d =” s t a r t o f f s e t ” o f f s e t =”0%” s t o p −c o l o r =”#00FF00 ” /> Nach der Veränderung durch JavaScript sieht das Bild dann wie in Bild 2.2 aus. Bild 2.2: SVG Grafik aus Listing 2.1 nach Modifizierung 9 In SVG werden nicht nur Farbverläufe unterstützt, sondern auch grafische Elemente. Dazu zählen Pfade, Kreise, Ellipsen, Rechtecke, Linien, Polygonzüge, Polygone, Texte und Bilder. Ein Pfad besteht aus einer beliebigen Kombination von Strecken, Ellipsebögen, quadratischen und kubischen Bézierkurven (Siehe: [ZIMM2012, S. 92ff]) sowie Neupositionierungen (ausführliche Beschreibung: [PaWiSwSt+2008]). Mit diesen Elementen können komplexe Strukturen definiert werden. SVG ist jedoch ein Dateiformat für zweidimensionale Vektorgrafiken. Es kann nur mit Hilfe von zusätzlichen Bibliotheken wie beispielsweise svg3d (siehe: [DEBE]) eine dritte Dimension hinzugefügt werden. Die komplette Berechnung vom Aufbau der SVG-Datei wird auf der CPU ausgeführt. 2.1.2 Application-Streaming und Anwendungsserver Die zweite Möglichkeit zur Visualisierung von 3D-Daten ist das Application-Streaming bzw. der Application-Server (Anwendungsserver). Ein Anwendungsserver (Application Server) ” erlaubt den Benutzern die Verwendung von Anwendungsprogrammen über das Netzwerk, die sich eigentlich auf dem Server befinden“ (vgl. [KERS2005, S. 557]). Der Datenbestand der Anwendung liegt bei der einfachsten Form des Anwendungsservers auf dem Server. Die Anwendung wird vor der Ausführung über das Netzwerk in den Arbeitsspeicher des Client-Computers geladen und dort lokal ausgeführt. Die nächst komplexere Form ist, Teile des Programms oder auch das komplette Programm auf dem Server auszuführen. Der Client würde dem Benutzer nur die Oberfläche des Programms anzeigen und alle Operationen direkt auf dem Server ausführen (vgl. [KERS2005, S. 557f]). Mit einem Anwendungsserver könnte die aktuelle wxWidgetGUI komplett auf dem Server ausgeführt werden und der Benutzer würde nur die Benutzeroberfläche sehen. Es gibt verschiedene Arten der Umsetzung dieser Technologie. Bei einigen davon kommt ein Browser für die Darstellung des Frontends zum Einsatz, bei anderen wird dafür eine spezielle Software benötigt. Weiter ist davon auszugehen, dass die wxWidgetGUI ohne Anpassungen an den Anwendungsserver nicht funktionieren wird. Programme für diese Art der Umsetzung sind XenApp (Citrix), App-V (Microsoft) und 2X ApplicationServer XG (2X), um nur einige Beispiele zu nennen. Alle hier genannten sind nur fähig Windows-Applikationen zu verarbeiten und daher nicht für den Prototypen einsetzbar. 10 Eine weitere Möglichkeit ist der X-Window-Server bzw. X-Server. Der X-Server stellt den ” Anwendungsprogrammen seine Dienste zur Verfügung, die darauf zugreifen, um Fenster und andere Komponenten der GUI darzustellen“ (vgl. [KERS2005, S. 557]). In diesem Szenario würde die wxWidgetGUI auf dem Server ausgeführt werden. Der Client würde seinen X-Server dem Server zur Verfügung stellen und dieser die Oberfläche auf dem Client X-Server darstellen. X-Server sind sowohl für Windows (z.B. Xming) als auch für Linux (z.B. XServer) verfügbar. 2.1.3 WebGL Die dritte und letzte vorgestellte Möglichkeit zur Visualisierung von Daten im Browser ist WebGL (Web Graphics Library). Diese ist eine plattformübergreifende, hardwarebeschleunigte 3D-Grafik-Programmierschnittstelle, die auf der Spezifikation von OpenGL ES 2.0 basiert und im Zusammenspiel mit JavaScript entwickelt wird. Die Khronos Gruppe und Mozilla sind Entwickler des lizenzfreien Standards. Bei der Khronos Gruppe handelt es sich um ein Industriekonsortium, das sich für die Erstellung und Verwaltung von offenen Standards im Multimedia-Bereich auf einer Vielzahl von Plattformen und Geräten einsetzt. Zu den über 100 Mitgliedern zählen unter anderem AMD, Intel, NVIDIA, Google sowie Oracle (vgl. [KHRO2014a]). Als Interface zum Document Object Modell (DOM) wird das HTML5 Canvas Element verwendet. Das Document Object Modell oder DOM ist die ” grundlegende API für das Arbeiten mit den Inhalten von HTML- und XML-Dokumenten“ (vgl. [FLAN2012, S. 390]). Für die Verwendung von WebGL im Browser werden keine zusätzlichen Plugins benötigt. Die unterstützten Browser und deren Versionen werden in Tabelle 2.2 aufgelistet. Tabelle 2.2: WebGL Unterstützung, Quelle: [DEVE2014b] Version Aktuelle Version Browser Internet Explorer 11.0 11.0 Mozilla Firefox 4.0 27.0 Google Chrome 8.0 32.0 Apple Safari 5.1 7.0 Opera 15.0 19.0 WebGL kann dazu verwendet werden, aufwändige Grafikanwendungen in den Browser zu integrieren. Beispiele hierfür sind die Visualisierung von Satellitenumlaufbahnen um die 11 Erde (Bild 2.3(c)), 3D Welten (Bild 2.3(b)) oder auch nur einige geometrische Formen im Raum (Bild 2.3(a)). (a) Geometrische Formen im Raum [BRAN2014] (b) 3D Welt [BETC2014] (c) Satellitenumlaufbahnen um die Erde [GILL2013] Bild 2.3: Beispiele für Grafikanwendungen im Browser Um WebGL im Browser zu nutzen können grundlegend zwei Ansätze verfolgt werden: • Reines WebGL • WebGL-Bibliotheken Bei der Verwendung von reinem WebGL stehen dem Entwickler alle WebGL API Funktionen des Browsers zur Verfügung. Alternativ dazu kann eine WebGL-Bibliothek verwendet werden, wovon bereits eine Vielzahl existiert. Die Khronos Gruppe hat eine Liste mit allen bekannten WebGL-Bibliotheken 12 erstellt. Diese beinhaltet 34 Bibliotheken, wovon jede eine unterschiedliche Zielgruppe besitzt (siehe: [KHRO2013a]). Manche davon können ausschließlich Dateien aus anderen Programmen importieren und darstellen, während andere nur eine sehr rudimentäre Funktionalität besitzen. Die Bekanntesten sind: • GLGE – www.glge.org (Abgerufen: 17.02.2014) • SceneJS – www.scenejs.org (Abgerufen: 17.02.2014) • CubicVR – www.cubicvr.org (Abgerufen: 17.02.2014) • Three.js – www.threejs.org (Abgerufen: 17.02.2014) Die in diesem Projekt verwendete Bibliothek heißt Three.js und wurde von Ricardo Cabello Miguel entwickelt. Three.js bietet eine einfache, intuitive Ansammlung von Objekten und Funktionen, die (auch normalerweise) im Bereich der 3D-Grafik angesiedelt sind. Durch die Verwendung von vielen best-practice Grafik-Engine-Techniken wird Three.js zu einer sehr schnellen Bibliothek. Weiterhin sind viele Helfer-Funktionen und Standard-Objekte bereits implementiert. Es handelt sich bei Three.js um ein Open-Source-Projekt, das auf GitHub ([CABE2014b]) liegt und sehr gut durch Ricardo Cabello Miguel und diverse andere Autoren gepflegt wird. GitHub ist ein Hosting-Dienst für Software-Projekte, als Versionsverwaltungssystem kommt Git zum Einsatz. Das Projekt ist unter der MIT Lizenz veröffentlicht. Weitere Vorteile von Three.js sind: • Verbergen der Details des 3D-Rendering. Abstraktion der einzelnen Aufrufe der WebGL API, so dass der Benutzer nur mit Gittern, Materialien und Lichtern arbeiten muss. • Die Bibliothek ist objektorientiert aufgebaut. • Unterstützung von Benutzerinteraktion. WebGL bietet keine native Möglichkeit für das Auswählen von Objekten im 3D Raum. Three.js hingegen biete volle Unterstützung. (vgl. [PARI2012, S. 17ff]) Weitere Informationen und eine Implementierung sind in Kapitel 4.2 auf Seite 46 zu finden. 13 2.1.4 Zwischenfazit Visualisierung In diesem Kapitel wurden die grundlegenden Technologien für die Visualisierung von 3DDaten im Web vorgestellt. Zunächst wurden die verschiedenen Möglichkeiten erklärt 3DDaten auf einem PC im Browser oder per Anwendung zu visualisieren. Ein Anwendungsserver stellt eine gute Möglichkeit dar, Applikationen über das Netzwerk auszuführen. Von den verschiedenen vorgestellten Möglichkeiten ist der X-Server die vielversprechendste. Da diese Technologie aber nicht im Browser und somit nicht im Web läuft, wird diese in der vorliegenden Arbeit nicht verwendet. SVG ist ebenso ein Ansatz, um die Visualisierung von 3D-Daten im Browser zu ermöglichen. Der Vorteil ist hierbei, dass sowohl auf PCs als auch auf Tablets die Unterstützung von SVG durch den Browser vorhanden ist. Dafür müssen allerdings Berechnungen für die darzustellenden Elemente, deren Rotation, Farbe und Transparenz komplett auf der CPU ausgeführt werden. Das kann zu hoher Last auf dem Client führen. Die Grafikkarte wird hierbei leider nicht verwendet. Ebenso ist SVG ein Dateiformat für 2D-Dateien und kann nur mit Hilfe einer zusätzlichen Bibliothek um die dritte Dimension erweitert werden. WebGL ist der letzte und wohl vielversprechendste Ansatz. Der Vorteil besteht darin, dass alle Berechnungen den 3D-Raum betreffend auf der Grafikkarte ausgeführt werden. Das führt zu einem großen Gewinn an Performance, im Vergleich zur reinen Ausführung auf der CPU. Ebenso wird die CPU entlastet und kann für andere Aufgaben verwendet werden. Durch die Verwendung von einer WebGL-Bibliothek wird das Entwickeln von Anwendungen mit WebGL deutlich vereinfacht. Durch eine höhere Abstraktionsstufe der WebGL API wird es möglich, WebGL als objektorientierte Bibliothek und nicht nur als Ansammlung von API-Funktionen zu nutzen. Aufgrund der Einfachheit und Erweiterbarkeit wurde WebGL den Alternativen ApplicationStreaming und SVG vorgezogen und wird in diesem Projekt verwendet. 2.2 HTML5 HTML5 ist eine der beiden Technologien, die die Grundlage für den Prototypen bilden. HTML5 ist die Bezeichnung für die neueste Version der HTML-Spezifikation, hat sich aber 14 auch zu einem Begriff für einen ganzen Satz von Technologien für Webanwendungen entwickelt, die als Teil von oder neben HTML (Hypertext Markup Language) entwickelt und in der Spezifikation festgehalten wurden (vgl. [FLAN2012, S. 712]). Eins dieser Elemente ist das HTML5 Canvas Element. Dieses stellt die Umgebung für das Zeichen zur Verfügung. Diese Umgebung bildet die Grundlage für WebGL (Siehe Kapitel 2.1.3 auf Seite 11). Ein weiteres Element ist WebSocket, welches bidirektionale Verbindungen im Browser ermöglicht (Siehe Kapitel 2.4.3 auf Seite 19). 2.3 JSON JSON ist die zweite der beiden Technologien, die die Grundlage für den Prototypen bilden. JSON“ steht für JavaScript Object Notation und ist ein Serialisierungsformat für Daten, ” das auf JavaScript-Literalen basiert und den Wert null, die boolschen Werte true und false, Gleitkommazahlen, Strings (Zeichenketten), Arrays mit Werten und String/WertZuordnungen beherrscht. Der elementare Wert undefined und die numerischen Werte NaN (Not a Number) und Infinity können mit JSON nicht dargestellt werden. Auch JavaScript-Funktionen, Date-, RegExps- und Error-Objekte werden nicht unterstüzt (vgl. [FLAN2012, S. 841]). Eine beispielhafte Verwendung von JSON in JavaScript sieht wie folgt aus: 1 // T e s t o b j e k t d e f i n i e r e n 2 var t e s t O b j e k t = {a : 1 , b :{ c : [ ” foo ” , n u l l ] } , d : f a l s e }; 3 4 v a r s = JSON . s t r i n g i f y ( t e s t O b j e k t ) ; 5 // s i s t ’ {” a ” : 1 , ” b ” : { ” c ” : [ ” f o o ” , n u l l ] } , ” d ” : f a l s e } ’ 6 7 v a r p = JSON . p a r s e ( s ) ; 8 // p i s t e i n e t i e f e K o p i e von t e s t O b j e k t 2.4 Datenübertragung zwischen Server und Browser In diesem Kapitel werden die verschiedenen Möglichkeiten zur Dateiübertragung zwischen Server und Browser verglichen. Diese werden anschließend für die Kommunikation zwischen dem FEMCore und der WebGUI verwendet. Zuerst werden die GET- und POST 15 Anfrage, dann das (long) polling und abschließend Websocket vorgestellt. HTTP steht für Hypertext Transfer Protocol (Hypertext-Übertragungsprotokoll). Es handelt sich dabei um das Netzwerkprotokoll zur Übertragung von Daten im Internet. Dabei ist es egal, ob es sich bei den Daten um Bilder, HTML-Dateien, 3D-Daten oder jegliche andere Art von Daten handelt. Diese Daten werden allgemein zu Ressourcen“ zusammen” gefasst. Meistens werden die Dateien über eine TCP/IP Socket Verbindung übertragen, allerdings ist auch UDP möglich. TCP/IP steht für Transmission Control Protocol; ein zuverlässiges Transportprotokoll der Internet-Protokollfamilie (vgl. [KERS2005, S. 1001]). UDP steht für User Datagram Protocol; ein schnelles, verbindungsloses Transportprotokoll der Internet-Protokollfamilie (vgl. [KERS2005, S. 1002]). HTTP nutzt wie die meisten Netzwerkprotokolle ein Client-Server-Modell, wobei es sich um ein zustandsloses Protokoll handelt; es werden keine Informationen über die vergangenen Verbindungen erhalten. Ein HTTP-Client öffnet die Verbindung zum Server und sendet eine Nachricht an diesen. Diese Nachricht enthält die Ressource, die vom Server abgefragt werden soll, und wird auch als request message bezeichnet. Der Server antwortet darauf mit der angeforderten Ressource in einer sogenannten response message. Nach dem Abschluss der Übertragung vom Server zum Client schließt der Server die Verbindung zu diesem. Die Anfrage an den Server kann einen der drei folgenden Befehle beinhalten: • GET • POST • HEAD GET und POST werden im nächsten Kapitel weitergehend erklärt, HEAD ist für die Dateiübertragung nicht von Interesse. (vgl. [JAME2012]) 2.4.1 GET- und POST-Anfragen Sowohl GET- als auch POST-Anfragen bestehen aus zwei Teilen: Kopfzeile (Header) und Inhalt (Body). In HTTP 1.0 können bis zu 16 und in HTTP 1.1 bis zu 46 Header angegeben werden. Bei HTTP 1.1 wird mindestens der Header Host: benötigt, bei HTTP 1.0 nicht. Da es sich bei HTTP 1.1 um den aktuellen Standard handelt, werden alle weiteren Beispiele und Erklärungen auf diesen beschränkt. Bei einer GET-Anfrage ist es meistens das Ziel Daten vom Server abzufragen, beispielsweise ein HTML Dokument. Eine GET-Anfrage für die Datei /pfad/datei.html auf dem Server www.testhost.de würde wie folgt aussehen: 16 1 GET / p f a d / d a t e i . html HTTP/ 1 . 1 2 Host : www. t e s t h o s t . de Es wird also das Kommando GET zusammen mit den Headern gesendet. In diesem Beispiel wird nur ein Header übertragen. Eine GET-Anfrage enthält keinen Inhalt (Body). Es werden also keine weiteren Daten nach den Kopfzeilen mehr angehängt. Um Daten an den Server zu übergeben, wird die URL nach dem Kommando GET erweitert. Es wird ein ?“ und danach Schlüssel/Wert-Paare &“-separiert angefügt. Eine GET-Anfrage für die ” ” Datei /pfad/datei.php auf dem Server www.testhost.de mit den Parametern name1 und name2 und den Werten wert1 und wert2 sieht wie folgt aus: 1 GET / p f a d / d a t e i . php ? name1=w e r t 1&name2=w e r t 2 HTTP/ 1 . 1 2 Host : www. t e s t h o s t . de Die Länge der Daten ist auf die maximal mögliche Länge der URL beschränkt. Dieser Wert liegt zwischen 255 und üblicherweise 8192 Bytes, kann aber auf dem Webserver eingestellt werden (vgl. [FiGeMoFr+1999, S. 14 (3.2.1)]). Die POST-Anfrage ist für das Übertragen von (Form-)Daten zu einem Server gedacht. Anders als bei der GET-Anfrage werden die Daten im Body übertragen und nicht in die URL kodiert. Üblich sind auch weitere Header wie Content-Type und Content-Length neben dem Host Header. Außerdem wird mit einer POST-Anfrage eher selten eine HTML Datei abgefragt. Es wird meistens ein Skript auf dem Server aufgerufen, beispielsweise ein PHPSkript, das die übergebenen Daten dann verarbeitet. PHP ist ein rekursives Akronym für PHP:Hypertext Preprocessor; eine verbreitete Sprache für serverseitige Web-Anwendungen ([KERS2005, S. 999]). Eine POST-Anfrage für die Datei /pfad/datei.php auf dem Server www.testhost.de mit den Parametern name1 und name2 und den Werten wert1 und wert2 sieht so aus: 1 POST / p f a d / d a t e i . php HTTP/ 1 . 1 2 Host : www. t e s t h o s t . de 3 Content −Type : a p p l i c a t i o n /x−www−form−u r l e n c o d e d 4 Content −L e n g t h : 23 5 6 name1=w e r t 1&name2=w e r t 2 Die Länge der Daten ist durch eine Einstellung im Webserver begrenzt. (vgl. [JAME2012]) Es können also sowohl mit einer GET- als auch mit einer POST-Anfrage Daten vom Client an den Server übertragen werden. Der Server kann jedoch nur mit dem Client kommunizieren, wenn dieser eine Anfrage an den Server sendet. Daraus folgt, dass, wenn der Server 17 Daten für den Client hat, dieser immer warten muss, bis sich der Client das nächste Mal mit ihm verbindet. Eine Lösung für dieses Problem ist das (long) polling, womit sich das nächste Kapitel beschäftigt. 2.4.2 (Long) Polling Eine erste mögliche Lösung für dieses Problem wäre das Pollen des Webservers (Siehe: Bild 2.4). Dabei verbindet sich der Client zum Server und dieser sendet gegebenenfalls bereitstehende Daten an den Client. Falls keine Daten vorhanden sind, wird die Verbindung geschlossen. Der Client überprüft somit periodisch, ob neue Inhalte vom Server verfügbar sind. Eine zweite Lösung für dieses Problem ist das sogenannte long polling (Siehe: Bild 2.5). Hierbei wird eine Anfrage an den Server gesendet, welcher die Verbindung bis zum Timeout geöffnet lässt. Anschließend sendet der Client Bild 2.4: Polling eine neue Anfrage an den Server. Das führt dazu, dass eine durchgehende Verbindung zwischen Server und Client besteht und beide Parteien Daten senden und empfangen können, wenn es nötig ist. Dies jedoch führt zu einer Vielfalt von neuen Problemen: • Der Server ist gezwungen, eine Vielzahl von TCP Verbindungen für jeden Client zu unterhalten: eine Verbindung wird zum Senden an den Client benötigt. Für jede ankommende Nachricht vom Client je eine weitere. • Es wird ein hoher Overhead produziert, da jede Nachricht einen Header enthält. • Der Client muss eine Zuordnung von ausgehenden zu eingehenden Verbindungen erstellen, um die Antworten korrekt zuordnen zu können. Eine einfachere Lösung für dieses Problem wäre eine einzelne TCP Verbindung, die für die Kommunikation in 18 Bild 2.5: Long-Polling beide Richtungen verwendet wird. Eine solche Möglichkeit bietet das WebSocket-Protokoll, welches im nächsten Kapitel thematisiert wird. (vgl. [FEME2011, S. 5ff] und [WEßE2011, S. 1f]) 2.4.3 WebSocket Das WebSocket Protokoll bietet eine einfache Möglichkeit, eine bidirektionale Verbindung zwischen Webanwendungen und einem WebSocket- oder einem Web-Server herzustellen. Der Web-Server muss hierbei das WebSocket Protokoll unterstützen. In Kombination mit der WebSocket API wird damit eine Alternative zum HTTP Polling für eine bidirektionale Kommunikation zur Verfügung gestellt. Das Protokoll wurde so entworfen, dass es bestehende bidirektionale Kommunikationstechnologien, die HTTP als Transport-Schicht nutzen, ablösen kann. WebSocket verwendet die Standard-HTTP-Ports 80 und 443 für TLS (Transport Layer Security; deutsch Transportschichtsicherheit), sodass der Verbindungsaufbau auch durch Firewalls hinweg funktioniert. Dabei sollen alle Vorteile der aktuellen Infrastruktur (Proxies, Filtering, Authentifizierung) weiter genutzt werden können. Alternativ dazu könnte eine andere Transportschicht (OSI-Modell Schicht 4 (siehe: [KERS2005, S. 543ff]) wie zum Beispiel TCP/IP oder UDP verwendet werden. Der Vorteil von HTTP als Transportschicht besteht darin, dass zwar die Implementierung um die WebSocket Funktionalität erweitert werden musste, aber der Handshake von HTTP übernommen werden kann. Durch einen Handshake werden die Parameter für den Kommunikationskanal zwischen Client und Server ausgehandelt. Ebenso ist es hierdurch möglich, sowohl HTTP(S) als auch WebSocket auf einem Server auf dem gleichen Port (80 oder 443) laufen zu lassen. Handshake und Datentransfer sind die zwei Teile des WebSocket Protokolls. Der Handshake ähnelt der GET-Anfrage von HTTP und wird in Listing 2.2 gezeigt. Listing 2.2: Handshake vom Client 1 GET / c h a t HTTP/ 1 . 1 2 Host : s e r v e r . e x a m p l e . com 3 Upgrade : w e b s o c k e t 4 C o n n e c t i o n : Upgrade 5 Sec−WebSocket−Key : dGhlIHNhbXBsZSBub25jZQ== 6 O r i g i n : h t t p : / / e x a m p l e . com 7 Sec−WebSocket−P r o t o c o l : ch a t , s u p e r c h a t 8 Sec−WebSocket−V e r s i o n : 13 19 Der Eröffnungs-Handshake ist so entworfen, dass er mit HTTP-Web-Servern kompatibel ist und somit sowohl HTTP Clients als auch WebSocket Clients mit dem gleichen Server über einen Port kommunizieren können. Als Parameter für den GET Befehl wird der Pfad zum WebSocket Endpunkt (Ressource) übergeben (Hier /chat). Wie bei der GET-Anfrage ist der Host Header zwingend notwendig. Dazu kommen die zwei neuen Header Upgrade und Connection, die dem Server mitteilen, dass der Client ein Upgrade auf das WebSocketProtokoll durchführen möchte. Sec-WebSocket-Key kommt außerdem als Header hinzu und wird für das Akzeptieren des Verbindungsaufbaus verwendet. Alle anderen Header sind optional. Weitere Header werden genutzt, um Optionen für das WebSocket Protokoll zu definieren. Der Header Origin enthält die Quelle der Anfrage. Falls der Server den Origin akzeptiert, wird eine Nachricht an den Client gesendet, dass die Verbindung akzeptiert wurde. Um dem Client zu zeigen, dass sein und nicht irgendein anderer Handshake akzeptiert wurde, wird eine Nachricht für den Client aus zwei Komponenten generiert. Die erste Komponente ist der Sec-WebSocket-Key, der mitgesendet wird. Dieser wird mit dem Globally Unique Identifier (GUID) 258EAFA5-E914-47DA” 95CA-C5AB0DC85B11“ verkettet. Aus dieser verketteten Zeichenkette wird dann ein SHA-1 Hash (siehe [EaJo2001]) generiert, welcher anschließend base64 (siehe [BRUE2003]) kodiert wird. Das Ergebnis aus diesen Operationen Bild 2.6: WebSocket Kommunikation wird dann dem Handshake als Header SecWebSocket-Accept beigefügt. Ebenso wird eins oder keins der Protokolle aus dem Client Handshake Header Sec-WebSocket-Protocol im Server Handshake Header Sec-WebSocketProtocol beigefügt. Der komplette Server Handshake wird in Listing 2.3 dargestellt. Listing 2.3: Handshake vom Server 1 HTTP/ 1 . 1 101 S w i t c h i n g P r o t o c o l s 2 Upgrade : w e b s o c k e t 3 C o n n e c t i o n : Upgrade 4 Sec−WebSocket−A c c e p t : s3pPLMBiTxaQ9kYGzzhZRbK+xOo= 20 5 Sec−WebSocket−P r o t o c o l : c h a t Nach dem erfolgreichen Handshake können sofort Daten übertragen werden (vgl. [FEME2011, S. 5ff] und [WEßE2011, S. 2]). Nachrichten werden bei WebSockets in Frames übertragen (siehe Bild 2.6 auf Seite 20)(weitere Informationen [RONA2012, Framing]) 2.4.4 Zwischenfazit Datenübertragung Der zweite große Bereich dieses Kapitels handelt von der Übertragung der Daten vom Client zum Server und umgekehrt. Hier wurden HTTP(S) und dessen POST- und GET-Request mit WebSocket verglichen. Da WebSocket die Weiterentwicklung von HTTP ist (wie oben beschrieben) und mit der Verwendung von WebSocket keine Lösungen wie (long) polling verwendet werden müssen, um eine bidirektionale Kommunikation zu erlangen, wurde WebSocket als Form der Übertragung gewählt. Das bringt außerdem den Vorteil, dass auf Serverseite nur ein WebSocket-Server und kein vollständiger HTTP-Server implementiert werden muss. Gerade die bidirektionale Kommunikation ist der ausschlaggebende Punkt, warum WebSocket der Alternative vorgezogen und in diesem Projekt verwendet wurde. Nachdem die Technologien für die Umsetzung feststehen, kann mit der Anforderungsanalyse für den Prototypen begonnen werden (Kapitel 3 auf Seite 24). Vorher werden jedoch noch die Echtzeit- und Performance-Anforderungen für Webapplikationen im folgenden Kapitel vorgestellt. 21 2.5 Echtzeit- und Performance-Anforderungen für Webapplikationen Hier werden die Echtzeit- und Performance Anforderungen für Webapplikationen und damit den Prototypen angerissen. Dabei wird versucht, diese Anforderungen basierend auf den Anforderungen für Desktop-Applikationen herzuleiten. Die erste Frage ist: Was ist Echtzeit? Die DIN 44300 definiert Echtzeit wie folgt: Unter Echtzeit versteht man den Betrieb ” eines Rechensystems, bei dem Programme zur Verarbeitung anfallender Daten ständig betriebsbereit sind, derart, dass die Verarbeitungsergebnisse innerhalb einer vorgegebenen Zeitspanne verfügbar sind. Die Daten können je nach Anwendungsfall nach einer zeitlich zufälligen Verteilung oder zu vorherbestimmten Zeitpunkten anfallen.“ (Siehe [SCHO2005, S. 39]) Im Duden sind zwei Beschreibungen für die Echtzeit zu finden: (EDV) vorgegebene Zeit, ” die bestimmte Prozesse einer elektronischen Rechenanlage in der Realität verbrauchen dürfen“ und simultan zur Realität ablaufende Zeit“ (vgl. [BIBL2013a]). Ebenfalls im Du” den findet man die folgende Definition für Echtzeitbetrieb: Arbeitsweise einer elektroni” schen Rechenanlage, bei der das Programm oder die Datenverarbeitung (nahezu) simultan mit den entsprechenden Prozessen in der Realität abläuft“ (vgl. [BIBL2013b]). Man kann also sagen, dass ein Programm, das in Echtzeit läuft, immer eine bestimmte Aufgabe in einer bestimmten Zeit zu erledigen hat. Generell kann die Echtzeit noch in zwei Unterkategorien aufgeteilt werden: die harte und die weiche Echtzeit. Bei der harten Echtzeit ist es ausschlaggebend, dass die definierte Aufgabe ohne Ausnahmen in dem dafür vorgesehenen Zeitraum ausgeführt wird. Ein Beispiel hierfür wäre eine elektronische Motorsteuerung, bei deren Versagen es zum Schaden oder sogar Ausfall des Motors kommen kann. Im Gegensatz zur harten Echtzeit kann es bei der weichen Echtzeit durchaus vorkommen, dass die Ausführung der Aufgabe etwas länger dauert als geplant. Der vorgegebene Zeitraum kann hier mehr als eine Art Richtlinie statt einer festen Vorgabe angesehen werden. Im Web können die Anforderungen für Desktop-Applikationen nur teilweise übernommen werden. Die grafische Benutzeroberfläche (GUI) von sowohl Web- als auch Desktop-Applikationen haben vieles gemeinsam. Beide sollen möglichst sofort reagieren, wenn der Benutzer mit diesen interagiert. Es darf zum Beispiel nicht der Fall sein, dass ein Button 22 geklickt wird und der Benutzer mehrere Sekunden auf das zu öffnende Fester warten muss (das kann natürlich vorkommen, sollte aber nicht der Regelfall sein). Da aber durchaus Antwortzeiten von bis zu einer Sekunde für einfache, zwei bis vier Sekunden für normale und acht bis zwölf Sekunden komplexe Aufgaben vom Benutzer als akzeptabel angesehen werden, handelt es sich hierbei um weiche Echtzeit (vgl. [HoDi2000, S. 23]). Im Gegensatz hierzu steht die Anbindung von Programm-Komponenten. Beispielsweise wird eine C++-Bibliothek betrachtet, die das Backend darstellt. Bei einer klassischen Desktop Applikation kann die grafische Benutzeroberfläche auch in C++ geschrieben und anschließend das Backend angebunden werden. Es kann so direkt und ohne Verzögerung auf alle Funktionen des Backends zugegriffen werden. Soll die GUI jedoch ins Web portiert werden, so ist es nicht mehr möglich, ohne Verzögerung auf das Backend zuzugreifen. Es muss mindestens die Zeit hinzuaddiert werden, die benötigt wird, um über eine beliebig geartete Schnittstelle mit dem Backend zu kommunizieren. Hier wird der erste Unterschied zwischen einer Desktop- und einer Web-Anwendung, was die Echtzeit betrifft, deutlich. Während es bei der Desktop-Anwendung möglich ist sofort auf das Backend zuzugreifen, kann bei der Web-Variante je nach Art der Schnittstelle eine zusätzliche Verzögerung auftreten. Auch hier handelt es sich um weiche Echtzeit. Die Anforderungen an die Performance für Web Applikationen bleiben dennoch die gleichen wie bei Desktop Applikationen: es soll möglichst schnell auf Funktionen zugegriffen werden können. Dabei ist es unwichtig, ob es sich um Funktionen für die GUI in JavaScript handelt oder um Funktionen - in welcher Sprache auch immer - auf dem Server, mit dem über das Internet, Intranet oder dem lokalen Computer kommuniziert wird. Ein weiterer Aspekt die Performance betreffend ist auch, dass es sich bei JavaScript um eine Interpreter-Sprache handelt. Der Code wird erst beim Laden der Website in Maschinensprache übersetzt und anschließend während der Ausführung immer weiter optimiert (Funktionsweise bei der V8 JavaScript-Engine von Google; bei anderen Browsern evtl. abweichend (vgl. [GOOG2014]). Im Gegensatz zu einer Desktop-Anwendung ist das natürlich ein deutlicher Nachteil. Diese Art von Anwendung wird bereits bevor sie ausgeführt wird kompiliert. Der Code kann somit vorher bereits beispielsweise durch den Compiler auf Geschwindigkeit optimiert werden. Für die Visualisierung von Daten gilt, dass diese mit mindestens 25 Bildern pro Sekunde (FPS) dargestellt werden müssen. Ab dieser Anzahl von Bilder sieht das menschliche Auge eine Bewegung / bewegte Bilder (Videos, Animationen) als flüssig an. Weitere Anforderungen für die Visualisierung von Daten neben den 25 FPS sind nicht vorhanden. 23 3 Anforderungsanalyse für den Prototypen In diesem Kapitel werden die Anforderungen für den Prototypen ermittelt. Dieser ist aus der wxWidgetGUI aus dem Projekt SimCloud abgleitet und wird als WebGUI bezeichnet. Das Kapitel ist unterteilt in zwei Bereiche. Zuerst werden die Anforderungen vorgestellt und anschließend die abgeleitete Software- und Systemarchitektur. 3.1 Anforderungen Die Anforderungen an die WebGUI lassen sich aus den Anforderungen der wxWidgetGUI ableiten. Die Anforderungen sind unterteilt in Bedienungsanforderungen“ (Kapitel 3.1.1) ” und Datenmenge und Performance“ (Kapitel 3.1.2). ” 3.1.1 Bedienungsanforderungen Die Applikation besteht aus fünf Elementen und passt sich damit dem Aussehen der wxWidgetGUI aus Bild 3.1 auf Seite 25 an. • Menüleiste – Oben • WebGL-Fenster – Mitte rechts • Projektbaum – Mitte links • Fehlerkonsole – Zusätzliche Komponente • Simulationsfortschrittsbalken – Zusätzliche Komponente Die Anforderungen an das Laden, Speichern und die Exportvorgänge sind die Folgenden: • Es kann eine STL Datei geladen werden und die Fehlermeldungen werden in der grafischen Benutzeroberfläche angezeigt. Alternativ kann auch eine gmsh- oder netgenDatei geladen werden. • Der Benutzer kann vor dem Laden einer Datei auswählen, ob es sich um eine gmshoder netgen-Datei handelt. 24 Bild 3.1: wxWidgetGUI aus dem Projekt SimCloud • Die aktuelle Ansicht kann als PNG Datei exportiert werden. Die Anforderung an die Interaktion mit dem Gitter sind die Folgenden: • Es ist möglich, die Kamera frei im Raum zu bewegen mittels WASD-Ansatz und +/für das Zoomen. Der WASD-Ansatz ist ein aus Computerspielen bekannter Ansatz zur Navigation durch eine 3D-Welt. Alternativ soll die Kamera frei im Raum durch das Bewegen der Maus und Zoomen durch das Mausrad bewegt werden. • Es ist möglich, einzelne Elemente mit einem Klick mit der linken Maustaste (LMT) auszuwählen (Bild 3.2 auf Seite 26). • Es ist ebenso möglich, mehrere Elemente mit zusätzlichem Halten der STRG-Taste auszuwählen. • Mit einem Klick mit der rechten Maustaste (RMT) können Aktionen mit den ausgewählten Punkten ausgeführt werden. – Bedaten eines oder mehrerer Punkte – Informationen über einen Punkt – Informationen zur Lösung eines Punktes 25 Bild 3.2: Einzelnes Element ausgewählt – Ausblenden eines oder mehrerer Punkte Bild 3.3(a) und 3.3(b) auf Seite 27 zeigen einen Entwurf für den Aufbau des Fensters, um ein Problem bzw. die Randwerte in der WebGUI zu definieren. Die weiteren Anforderungen sind: • Es stehen Dialoge zur globalen Bedatung zur Verfügung. • Es können analytische Funktionen als Parameter angegeben werden, die durch das Backend auf Fehler überprüft werden. • Dirichlet- und Neumann-Randwerte stehen in der GUI zur Bedatung zur Verfügung. Anforderungen an die Auswahl von Parametern für die Simulation: • Es ist möglich, die Simulation zu starten. Bild 3.4 auf Seite 27 zeigt einen Entwurf für den Aufbau des Fensters für die Hardwaredaten. Die weiteren Anforderungen sind: • Es wird eine Liste mit allen verfügbaren (Cloud-) Servern angezeigt. • Es kann pro Server die Anzahl der benötigten Prozessoren ausgewählt werden. 26 (a) Problem definieren (b) Randwerte definieren Bild 3.3: Problem und Randwerte definieren Bild 3.4: Entwurf des Hardware-Dialogs • Es kann ein underlying problem aktiviert werden und es steht anschließend On Host oder On an external cloud zur Auswahl. Anforderung an die Visualisierung von Gittern ohne vorliegende Lösung: • Ein geladenes Gitter kann als reines Gittermodell (nur Linien) dargestellt werden. Anforderung an die Visualisierung von Lösungswerten: • Skalare Werte Anforderungen an die Fehlerkonsole: • zeigt Fehler und Warnungen an, die auftreten können • zeigt möglichst viele Informationen zum Simulationsverlauf an 27 3.1.2 Datenmenge und Performance In diesem Kapitel werden die Anforderungen an die Datenmenge für den Prototypen zusammengetragen. Bei den analysierten Datenmengen handelt es sich ausschließlich um die Daten, die zwischen Client und Server übertragen werden. Weitergehend wird auch die Performance mit in die Betrachtung einbezogen. Als Grundlage für die Berechnungen werden die Upload- und Download-Geschwindigkeiten aus Tabelle 3.1 verwendet (Quelle: [TCOM2014]). Tabelle 3.1: Up- und Download Geschwindigkeit von DSL 6000 bis VDSL 50 Download Upload Beschreibung [kBit/s] [kByte/s] [kBit/s] [kByte/s] DSL 6000 6.016 752 576 75 16.000 2.000 1.024 128 DSL 16000 VSDL 25 25.064 3.133 5.120 640 VSDL 50 51.392 6.424 10.240 1.280 Zusätzlich werden die vier Gitterdateien aus Tabelle 3.2 für diesen Test generiert. Dafür wird die Datei shaft.vol so lange mit dem Gitter-Generator Netgen (Weitere Informationen: [SCHO2014]) verfeinert, bis die gewünschte Anzahl der Elemente erreicht ist (hier 106 Elemente). Tabelle 3.2: Eigenschaften der Gitterdateien für den Performance-Test Dateiname Anzahl der Elemente Dateigröße [kB] shaft.vol 2.624 387 shaft 20k.vol 20.992 1.984 shaft 167k.vol 167.936 12.575 shaft 1.3m.vol 1.343.488 89.525 Für die weitergehenden Berechnungen wird von einem Benutzer mit DSL 6000 bis einschließlich VDSL 50 ausgegangen. Die Übertragung der Dateien vom Client zum Server kann berechnet werden, indem die Größe der verschiedenen Dateien aus Tabelle 3.2 durch die Upload-Geschwindigkeit aus Tabelle 3.1 geteilt wird. Das Ergebnis dieser Berechnung(en) ist in Tabelle 3.3 auf Seite 29 dargestellt. 28 Tabelle 3.3: Übertragungsdauer der Gitter-Datein (Tabelle 3.2) zum Server Dauer [s] Dateiname DSL 6000 DSL 16000 VDSL 25 VDSL 50 shaft.vol 5,16 3,02 0,60 0,30 shaft 20k.vol 26,45 15,50 3,10 1,55 167,67 98,24 19,65 9,82 shaft 167k.vol shaft 1.3m.vol 1193,67 699,41 139,88 69,84 Basierend auf der Dauer für den Upload der verschiedenen Dateien sind die Anforderungen an den Upload auch davon abhängig, welche Probleme der Benutzer primär bearbeiten möchte. Falls nur Probleme mit weniger als 200.000 Elementen bearbeiten werden sollen, reicht DSL 6000 aus. Es müsste allerdings eine Übertragung von bis zu drei Minuten vom Benutzer als akzeptabel angesehen werden. Für Benutzer, die hauptsächlich mit großen Problemen (im Bereich 106 ) arbeiten, sollte mindestens VDSL 25 zur Verfügung stehen, da sonst Upload Zeiten von bis zu 10 (DSL 16000) bzw. 20 (DSL 6000) Minuten durchaus wahrscheinlich wären. Damit ist die Anforderung für das Projekt, nur aus Sicht des Uploads betrachtet, DSL 6000 oder mehr, abhängig von der Geduld des Benutzers. Als nächstes wird die Download-Dauer für die verschiedenen Dateien berechnet. Dafür werden die Werte in der Optimierung D aus Tabelle 4.2 (Seite 44) verwendet. Aus Gründen der Übersichtlichkeit sind die Werte noch einmal in Tabelle 3.4 aufgeführt. Tabelle 3.4: Größe der Antworten der verschieden Dateien Optimierung D [MB] Dateiname shaft.vol 1,01 shaft 20k.vol 8,08 shaft 167k.vol 63,11 shaft 1.3m.vol 501,88 Auch hier berechnet sich die Übertragungszeit, indem man die Dateigröße aus Tabelle 3.4 durch die Download-Geschwindigkeit aus Tabelle 3.1 auf Seite 28 teilt. Das Ergebnis dieser Berechnung(en) ist in Tabelle 3.5 auf Seite 30 dargestellt. 29 Tabelle 3.5: Übertragungsdauer der Gitter-Dateien (Tabelle 3.4) zum Client Dauer [s] Dateiname DSL 6000 DSL 16000 VDSL 25 VDSL 50 shaft.vol 1,46 0,55 0,35 0,17 shaft 20k.vol 10,74 4,04 2,58 1,26 83,92 31,56 20,14 9,82 shaft 167k.vol shaft 1.3m.vol 667,39 250,94 160,19 78,13 Wie bereits bei der Upload-Dauer kann auch hier wieder in zwei Bereiche unterteilt werden. Arbeitet ein Benutzer hauptsächlich mit kleinen Problemen bis maximal 200.000 Elementen, reicht auch hier, wie beim Upload, DSL 6000 aus. Werden größere (Im Bereich 106 ) Probleme bearbeitet, sollte auch hier mindestens VDSL 25 zur Verfügung stehen. Wenn jetzt der Up- und der Download zusammengenommen werden, kann man festlegen: es wird mindestens DSL 6000 für kleinere und VDSL 25 für größere Probleme benötigt. Zu der Dauer für die Simulation an sich können noch keine Aussagen gemacht werden, da diese Funktion noch nicht vollständig im Backend implementiert ist. Die Anforderungen an das Zeichnen der Daten nach dem Empfangen sind relativ kurz. Nachdem die Daten vollständig empfangen sind, sollen diese möglichst schnell für den Benutzer zur Bearbeitung bereit sein. Bei kleineren Gittern sollten daher nicht mehr als zwei Sekunden vergehen, in denen das komplette Gitter erstellt und anschließend für den Benutzer sichtbar dargestellt wird. Bei größeren Gittern sollte ein Maximum von zwölf Sekunden nicht überschritten werden (vgl. [HoDi2000, S. 23]). Diese Werte können natürlich je nach Hardware des Client PC stark variieren. Es bietet sich an, einen Fortschritt für den Nutzer anzuzeigen, um die verbleibende Zeit abschätzen zu können. Für die folgenden Aktionen sollte die gewünschte Aktion (nach Möglichkeit) gefühlt sofort ausgeführt werden: • Ein- und Ausblenden von Elementen (Transparent schalten) • Auswählen von einzelnen Elementen (Bedatung / Ausblenden) • Auswählen von mehreren Elementen (Bedatung / Ausblenden) 30 3.1.3 Sicherheit Ein Ziel des Projekt SimCloud ist, eine Cloud-Anwendung zu erzeugen, die einen hohen Anspruch an Sicherheit hat. Da mit dieser Software unternehmenskritische Dateien (beispielsweise das Layout des neuen Türschlosses, das der Konkurrent nicht kennen darf) erst auf den Hauptserver und von dort aus in kleineren Teilen auf die Cloud-Server verteilt werden, ist es besonders wichtig, dass der komplette Weg vor Angriffen geschützt ist. Die Verbindung zwischen Hauptserver und den Cloud-Servern ist hier eher unkritisch, da das zu lösende Problem in kleine Einzelprobleme zerlegt wird und nur die Berechnung an die Cloud-Server delegiert wird. Die Verbindung vom Arbeitsplatz PC (Client) zum Hauptserver hingegen ist durchaus relevant. Die erste Möglichkeit, die Sicherheit zu gewährleisten, wäre den Hauptserver in das Unternehmensnetzwerk via z.B. VPN Strecke aufzunehmen. Dies würde sicherstellen, dass die Daten, die an den Server gesendet werden, komplett durch einen gesicherten Tunnel übertragen werden. Die zweite Möglichkeit besteht darin, die komplette Kommunikation zwischen Client und Hauptserver zu verschlüsseln. Das WebSocket-Protokoll unterstützt verschlüsselte Verbindungen (TLS-Verbindungen) zwischen Client und Server. Durch die Aktivierung dieser Funktion im FEMInterface kann die Sicherheit der Übertragung sichergestellt werden. 31 3.2 Abgeleitete Software- und Systemarchitektur In diesem Kapitel wird die Software- und Systemarchitektur für den Prototypen vorgestellt. Diese wird aus der Architektur der wxWidgetGUI abgeleitet, welche bereits alle relevanten Komponenten des Host-Software-Backends sowie die Anbindung an die Solver und die wxWidgetGUI beinhaltet. Alle Komponenten des Host-Software-Backends werden unter dem Begriff FEMCore zusammengefasst. Als Darstellungsform wird die Unified Modeling Language (Vereinheitlichte Modellierungssprache), kurz UML, verwendet, aus der das Komponentendiagramm gewählt wird. Das Komponentendiagramm (engl. component diagram) stellt verschiede” ne Bestandteile eines Systems als Komponenten dar. Es zeigt die Komponenten, wie sie zur Laufzeit organisiert sind, und die sich daraus ergebenden Abhängigkeiten“ (weitere Informationen: [RUQU2012, S. 216ff]). Aus dem Komponentendiagramm werden die beiden Objekte Komponente und Schnittstelle verwendet. Bild 3.5: Softwarearchitektur Wie Bild 3.5 zeigt, ist die aktuelle GUI (xwWidgetGUI) an die Programmteile Gitterverwaltung und Simulationssteuerung angebunden. Exakt auf diese beiden Programmteile benötigt auch die WebGUI Zugriff. Da es keine Möglichkeit gibt, direkt aus dem Browser 32 via JavaScript auf die C++-Bibliotheken zuzugreifen, muss eine Lösung für dieses Problem gefunden werden. Zuerst wird eine Schnittstelle definiert, die den Informationsaustausch zwischen WebGUI und Gitterverwaltung bzw. Simulationssteuerung möglich macht. Diese Schnittstelle heißt FEMInterface und ist in C++ geschrieben. Sie erfüllt die folgenden Aufgaben: • Bereitstellen eines WebSocket Servers, um die Kommunikation zwischen (Web)Client und FEMInterface (Server) zu ermöglichen. • Anbinden des FEMCore, um eine Kommunikation von dem FEMInterface mit Gitterverwaltung bzw. Simulationssteuerung zu ermöglichen. Diese beiden Funktionen des FEMInterface machen es möglich, dass ein WebSocket-Client sich mit dem Server verbinden und durch dieses sowohl die Gitterverwaltung als auch die Simulationssteuerung ansprechen kann. Bild 3.6: Softwarearchitektur (Neu) Das Format für die Kommunikation sollte möglichst einfach und leicht verständlich gehalten werden. Daher werden im Client Nachrichtenobjekte generiert und diese vor der 33 Übertragung mit JSON serialisiert. Auf der Serverseite werden die Nachrichten dann deserialisiert und anschließend verarbeitet. Nachrichten zurück an den Client werden auch wieder im JSON-Format übertragen, so dass diese vom Client wieder deserialisiert werden können. Da bei der Serialisierung von Objekten in JSON ein relativ großer Overhead entsteht, werden große Dateien (nur relevant für Gitterdaten) in einem Rohdatenformat (RAW-Format) übertragen. Weitere Informationen zu den Datenformaten und dem Overhead können in Kapitel 4.2.3 auf Seite 40 nachgelesen werden. Das Bild 3.6 auf Seite 33 zeigt die Architektur, nachdem die WebGUI mit angebunden wurde. 34 4 Technische Umsetzung Die Umsetzung der Software- und Systemarchitektur aus Kapitel 3 wird in diesem Kapitel thematisiert. Dabei wird zuerst vertieft auf die Technologie WebGL (Kapitel 4.1) eingegangen. Anschließend werden die Visualisierung von 3D-Daten (Kapitel 4.2), die Umsetzung der sonstigen GUI-Elemente (Kapitel 4.3) und zuletzt die möglichen Techniken zur Verbesserung der Datenübertragung (Kapitel 4.4) vorgestellt. 4.1 WebGL – Detailliertere Technologiebetrachung Hier wird die Technologie WebGL tiefergehend betrachtet. Dabei wird der Unterschied zu OpenGL und die Interaktion mit JavaScript näher erläutert. 4.1.1 Gemeinsamkeiten und Unterschiede zu OpenGL Wie bereits in Kapitel 2.1.3 auf Seite 11 erwähnt, basiert WebGL auf der Spezifikation von OpenGL ES 2.0 und behält dabei deren Semantik. Allerdings gibt es einige signifikante Unterschiede im Verhalten zwischen OpenGL ES 2.0 (WebGL) und der OpenGL API auf Desktop-Systemen, welche hier jedoch nicht weiter thematisiert werden, da sie sich nicht auf die Implementierung der WebGUI des SimCloud Projekts auswirken. Weitere Informationen und Codebeispiele können in der Quelle nachgeschaut werden. [KHRO2012] 4.1.2 Interaktion mit JavaSkript Der Browser implementiert WebGL, indem er eine WebGL Schnittstelle für JavaScript anbietet, welche die Implementierung der OpenGL-ES-Spezifikation ist. WebGL wird in JavaScript ausschließlich durch die WebGL-JavaScript-Schnittstelle angesprochen. Der Grafiktreiber unterstützt die Implementierung von OpenGL ES und führt den Code aus. Wird jetzt WebGL durch JavaScript angesprochen, so übersetzt die Schnittstelle die Befehle und leitet diese an die Grafikkarte weiter, wo sie ausgeführt werden. (vgl. [PARI2012, S. 2f] und [SINI2011]) 35 4.2 3D Visualisierung der Simulationsdaten Die verschiedenen Schritte, die unternommen werden müssen, um die WebGUI basierend auf den Anforderungen aus Kapitel 3 umzusetzen, werden in diesem Kapitel erläutert. Die verwendeten Bibliotheken sind Thema im folgenden Kapitel 4.2.1. Mithilfe dieser Bibliotheken wird der FEMCore mit der WebGUI über das FEMInterface verbunden. Als Übertragungsformat wird dabei JSON verwendet, welches im Kapitel 4.2.3 definiert wird. Dies führt zu einer Herausforderung, da aufgrund der großen Menge an Overhead bei JSON ein weiteres Übertragungsformat für besonders große Dateien (Gitter-Dateien) benötigt wird, welches in Kapitel 4.2.4 thematisiert wird. Mit der Erklärung der Implementierung des WebGL-Teils wird in Kapitel 4.2.6 begonnen. Anschließend wird ein Vergleich zwischen reinem WebGL mit der Bibliothek Three.js aufgestellt. Basierend auf den Anforderungen an das Auswählen und Ausblenden von Elementen aus Kapitel 3 wird im Kapitel 4.2.7 vorgestellt, wie Transparenz für einzelne Punkte in Three.js möglich ist. Basierend auf diesen Erkenntnissen wird in Kapitel 4.2.8 das Auswählen von Elementen und in Kapitel 4.2.9 das Ausblenden von Elementen vorgestellt. Abschließend werden noch in Kapitel 4.2.10 die verschiedenen Möglichkeiten zur Umsetzung einer Legende erklärt. 4.2.1 Verwendete Bibliotheken Der erste Schritt der Umsetzung ist herauszufinden, welche Bibliotheken für das Projekt verwendet werden können. Die Bibliotheken leiten sich dabei aus der Software- und Systemarchitektur, welche in Kapitel 3.2 auf Seite 32 behandelt wurde, ab. Da sich die Implementierung für die folgenden Komponenten nach einer kurzen Recherche als sehr aufwendig herausstellt, werden diese durch Externe ersetzt. • WebSocket-Server • JSON-(De)Serialisierer Die Serialisierung eines Objekts ist ein Vorgang, bei dem der Zustand eines Objekts in ” einen String umgewandelt wird, aus dem es später wiederhergestellt werden kann“ (vgl. [FLAN2012, S. 148]). Da es eine Vielzahl von WebSocket-Server Implementierungen zur Auswahl gibt (sowohl in C als auch in C++), wurde sich hier auf einige wesentliche beschränkt. Die Folgenden werden anschließend näher betrachtet: 36 • libwebsockets – http://libwebsockets.org/trac/libwebsockets C-Bibliothek mit ws(s) und http(s) Unterstützung • websocketpp – http://www.zaphoyd.com/websocketpp C++-Bibliothek mit ws(s) Unterstützung • WebSocketServer – http://websocketserver.de C++-Bibliothek mit ws und ohne wss Unterstützung. Server wird in LUA-Skript geschrieben Durch das Fehlen der Unterstützung für wss (WebSocket Verbindungen über https) und dem Zwang den Server in LUA-Skript zu schreiben, fällt WebSocketServer direkt aus der Auswahl heraus. Da die anderen Komponenten des Projekts (FEMCore) bereits in C++ geschrieben sind, bietet es sich an auch für die Bibliotheken C++ zu verwenden, weil so eine Vermischung von Code zwischen C und C++ Code vermieden wird. Da libwebsockets eine C-Bibliothek ist, fällt diese somit auch heraus. Für das Projekt wird also die Bibliothek WebSocket++ gewählt. Auch JSON-(De)Serialisierer gibt es in großer Zahl, wie eine kurze Recherche zeigt. Wie zuvor wird sich auch hier auf einige wenige, vielversprechende Implementierungen für einen ersten Vergleich konzentriert: • JSON Spirit – http://www.codeproject.com/Articles/20027/JSON-Spirit-A-C-JSON-ParserGenerator-Implemented • libjson – http://sourceforge.net/projects/libjson/ • JsonCpp – http://jsoncpp.sourceforge.net/ Alle drei Bibliotheken erfüllen die Anforderungen für das Projekt. Es wird sich hier deshalb bei der Auswahl darauf beschränkt, die Bibliothek zu verwenden, bei der die Bedienung am intuitivsten erscheint. Sowohl JSON Spirit als auch JsonCpp sind, basierend auf den Beispielen, nicht einfach verständlich. Die Beispiele können auf den Internetseiten der Bibliotheken eingesehen werden. Ausschließlich bei libjson sind die Beispiele nur nach dem Download der Zip-Datei von der Internetseite einsehbar. Ein erster Blick auf die Beispiele von libjson hingegen lässt sofort darauf schließen, wie die Funktionen zu nutzen sind. Die Auswahl fällt daher auf libjson. 37 Nachdem die Auswahl der Bibliotheken getroffen ist, wird damit begonnen den Kommunikationskanal zwischen Client und Server (FEMInterface) zu implementieren. Auf der Serverseite kommt dafür WebSocket++ zum Einsatz, während im Client hierfür die WebSocket API des Browsers verwendet wird. Die grundlegende Server-Implementierung besteht aus der Funktion main() und einer Funktion on message() für die ankommenden Nachrichten (Siehe: Listing 4.1). Listing 4.1: Rudimentäre Websocket Server Implementierung 1 v oi d on message ( s e r v e r ∗ s , websocketpp : : c o n n e c t i o n h d l hdl , m e s s a g e p t r msg ) { 2 3 // I n h a l t d e r N a c h r i c h t a b f r a g e n 4 s t d : : s t r i n g message = msg−>g e t p a y l o a d ( ) ; 5 // N a c h r i c h t z u r ü ck an den C l i e n t s e n d e n 6 s−>s e n d ( h d l , message , w e b s o c k e t p p : : f r a m e : : opcode : : TEXT ) ; 7 } 8 9 i n t main ( ) { // S e r v e r −Endpunkt e r s t e l l e n 10 server fem server ; 11 try { 12 // L o g g i n g a k t i v i e r e n 13 f e m s e r v e r . s e t a c c e s s c h a n n e l s ( websocketpp : : log : : a l e v e l : : a l l ) ; 14 fem server . clear access channels ( 15 websocketpp : : log : : a l e v e l : : frame payload ) ; 16 17 // ASIO i n i t i a l i s i e r e n 18 fem server . i n i t a s i o (); 19 20 // N a c h r i c h t e n −H a n d l e r r e g i s t r i e r e n 21 fem server . set message handler ( 22 b i n d (& on me ss age , &f e m s e r v e r , : : 1 , : : 2 ) ) ; 23 24 // Auf P o r t 9002 hö r e n 25 fem server . l i s t e n (9002); 26 27 // B e g i n n e n V e r b i n d u n g e n zu a k z e p t i e r e n 28 fem server . start accept (); 29 38 30 // S e r v e r s t a r t e n 31 f e m s e r v e r . run ( ) ; } catch ( const std : : e x c e p t i o n & e ) { 32 s t d : : c o u t << e . what ( ) << s t d : : e n d l ; 33 } catch ( websocketpp : : l i b : : e r r o r c o d e & e ) { 34 s t d : : c o u t << e . message ( ) << s t d : : e n d l ; 35 } catch ( . . . ) { 36 s t d : : c o u t << ” o t h e r e x c e p t i o n ” << s t d : : e n d l ; 37 } 38 39 } Im Client wird dafür ein WebSocket-Objekt generiert, welches das Gegenstück zum Server darstellt. Das Objekt connection wird global deklariert, sodass alle Funktionen darauf Zugriff haben. In der Funktion initWebSocket() wird das Objekt dann initialisiert. Anschließend werden die benötigten Events (onopen, onerror und onmessage) angebunden. Über das Objekt connection wird die komplette Kommunikation mit dem Server abgewickelt (Siehe: Listing 4.2). Listing 4.2: Rudimentäre Websocket Client Implementierung 1 var connection = null ; 2 function initWebSocket () 3 { 4 c o n n e c t i o n = new WebSocket ( ’ ws : / / 1 2 7 . 0 . 0 . 1 : 9 0 0 2 / ’ ) ; 5 // Nachdem d i e V e r b i n d u n g ge ö f f n e t i s t : Daten s e n d e n 6 c o n n e c t i o n . onopen = f u n c t i o n ( e ) { 7 connection . send (” H e l l o S e r v e r ” ) ; 8 }; 9 10 // F e h l e r l o g g e n 11 connection . onerror = function ( error ) { 12 v a r message = ” F a i l e d t o c o n n e c t t o : ” + e r r o r . t a r g e t . u r l ; 13 c o n s o l e . l o g ( message ) ; 14 }; 15 16 // N a c h r i c h g e n l o g g e n 17 c o n n e c t i o n . onmessage = f u n c t i o n ( e ) { 18 c o n s o l e . log ( e . data ) ; 39 }; 19 20 } Aus Gründen der Übersichtlichkeit werden hier nur Code-Ausschnitte angezeigt. Mit der Kombination von Listing 4.1 und 4.2 ist es möglich, Nachrichten zwischen Client und Server auszutauschen. Wie bereits in Kapitel 2.4.3 auf Seite 19 erwähnt, handelt es sich dabei um eine bidirektionale Verbindung zwischen Client und Server. 4.2.2 Anbinden des Backends Um die Verbindung zwischen Web-Client und dem Host-Software-Backend (FEMCore) abzuschließen, muss der FEMCore an das FEMInterface angebunden werden. Beide werden in C++ entwickelt, weshalb die Anbindung kein größeres Problem darstellt. Es muss lediglich dem FEMInterface-Projekt das FEMCore-Projekt als Referenz hinzugefügt werden, und anschließend kann auf die gewünschten Funktionen zugegriffen werden. Die Formulierung Projekt“ wird hier verwendet als Projekt in einer Entwicklungsumgebung“ (IDE) wie in ” ” diesem Fall Eclipse. Nachdem der Kommunikationskanal erfolgreich umgesetzt ist, kann damit begonnen werden das Datenformat zu entwerfen und anschließend zu implementieren. 4.2.3 Das Übertragunsformat In der ersten Version sind sämtliche Nachrichten im JSON-Format. Alle Objekte werden über einen Typ unterschieden und haben unterschiedlich viele weitere Parameter. Die Parameter für jeden Typ werden in Tabelle 4.1 auf Seite 41 aufgelistet. Eine Beispiel-Nachricht an den Server vom Typ setproblem sieht wie folgt aus: 1 // N a c h r i c h t e n −O b j e k t d e f i n i e r e n 2 v a r message = { } ; 3 // P a r a m e t e r z u w e i s e n 4 message . t y p = ” s e t p r o b l e m ” ; 5 message . kx = ” x ˆ 2 ” ; 6 message . ky = ” y ˆ 2 ” ; 7 message . kz = ” z ˆ 2 ” ; 8 message . f = ” xˆ2+yˆ2+z ” ; 9 // O b j e k t i n JSON−S t r i n g s e r i a l i s i e r e n 10 40 v a r s t r i n g M e s s a g e = JSON . s t r i n g i f y ( message ) ; Tabelle 4.1: Parameter der JSON-Objekte Typ Paramter Beschreibung grid Keine Parameter solution Keine Parameter materials Keine Parameter setting setting Name der Einstellung, value Wert der Einstellung setboundary facetId Element-ID neumann Funktions-String für: Neumann dirichlet Funktions-String für: Dirichlet robin1 Funktions-String für: Robin1 rbin2 Funktions-String für: Robin2 robin3 Funktions-String für: Robin3 setproblem r Funktions-String für: r kx Funktions-String für: kx ky Funktions-String für: ky kz Funktions-String für: kz f Funktions-String für: f setmaterial facetId Element-ID material Material dimension Dimension validatefunction function Funktions-String id ID für die Identification im Client 11 // ’ {” t y p ” : ” s e t p r o b l e m ” , ” kx ” : ” x ˆ 2 ” , ” ky ” : ” y ˆ 2 ” , ” kz ” : ” z ˆ 2 ” , 12 // ” f ” : ” xˆ2+yˆ2+z ”} ’ Der Server antwortet auf alle Nachrichten (außer dem Typ grid und solution) mit der gleichen Nachricht, es werden allerdings zwei zusätzliche Parameter angefügt. Valid und error message geben dem Client Informationen darüber, ob sein Befehl erfolgreich auf dem Server ausgeführt wurde oder nicht. Für die Nachrichtentypen grid und solution antwortet der Server mit einem Array aus Dreiecken. Ein Array ist eine geordnete Sammlung von ” Werten. Die einzelnen Werte bezeichnet man als Elemente. Jedes Element hat eine numerische Position im Array, die als Index bezeichnet wird.“ (vgl. [FLAN2012, S. 151]). Jedes Dreieck enthält wiederum Informationen zu den drei zugehörigen Punkten. Nachdem das Datenformat fertig spezifiziert ist, werden einige Tests durchgeführt, um die Leistungsfähigkeit des Formates zu überprüfen. Für diese Tests werden Testdateien generiert, die in Tabelle 4.2 aufgelistet sind. Für 167 k und 1,3 m Elemente können keine Werte festgestellt werden, da das Backend diese Dateien sehr langsam einliest und die 41 Konvertierung zu JSON-Objekten ebenso sehr zeitaufwendig ist. Die Tests werden jeweils nach etwas über zehn Minuten abgebrochen (Tabelle 4.2 Optimierung: Ohne). Eine erste Optimierung der Größe ist, von der write formated-Funktion auf die write-Funktion der Bibliothek libjson zu wechseln. Der Unterschied besteht darin, dass die erste Funktion den JSON-String formatiert und die zweite den String am Stück schreibt (Siehe Listing 4.3). Listing 4.3: Vergleich write und write formated 1 var t e s t = { 2 ” S t r i n g Node ” : ” S t r i n g V a l u e ” , 3 ” I n t e g e r Node ” : 4 2 , 4 ” F l o a t i n g P o i n t Node ” : 3 . 1 4 , 5 ” B o o l e a n Node ” : t r u e 6 } 7 // Ausgabe m i t w r i t e f o r m a t e d ( ) 8 ’{ ” S t r i n g Node ” : ” S t r i n g V a l u e ” , 9 10 ” I n t e g e r Node ” : 4 2 , 11 ” F l o a t i n g P o i n t Node ” : 3 . 1 4 , 12 ” B o o l e a n Node ” : t r u e 13 }’ 14 // Ausgabe m i t w r i t e ( ) 15 // Z e i l e n u m b r u c h i s t n u r f ü r d i e U e b e r s i c h t l i c h k e i t 16 ’ {” S t r i n g Node ” : ” S t r i n g V a l u e ” , ” I n t e g e r Node ” : 4 2 , 17 ” F l o a t i n g P o i n t Node ” : 3 . 1 4 , ” B o o l e a n Node ” : t r u e } ’ Weiterhin werden alle Beschriftungen für die Werte der Punkte entfernt, da diese nur der Lesbarkeit dienlich sind (Siehe Listing 4.4). Listing 4.4: Vergleich mit und ohne Beschriftung 1 var punktVorher = { 2 x : 42 , 3 y : 43 , 4 z : 44 , 5 i d : 31 , 6 r : 165 , 7 g : 165 , 8 b : 165 , 9 a: 0, 42 v : 513 10 11 } 12 13 var punktNachher = [ 14 42 , 15 43 , 16 44 , 17 31 , 18 165 , 19 165 , 20 165 , 21 0, 22 513 23 ] Durch die beiden Änderungen aus Listing 4.3 und 4.4 wird die Größe um bis zu 55% reduziert (Tabelle 4.2 Optimierung: A). Es wird allerdings weiterhin versucht die Dateien zu verkleinern, da sie noch immer recht groß sind. Eine weitere Optimierung der Größe und gleichzeitiges Beibehalten des JSON-Formates ist nicht möglich. Obwohl alle überflüssigen Leerzeichen und Bezeichner entfernt sind, reichen die 12 GB Arbeitsspeicher des Testsystems bei Tests nicht aus, um die Datei mit 1,3 Millionen Elementen zu JSON zu konvertieren. Diese beiden Punkte führen dazu, dass ein anderer Ansatz verfolgt wird. Dieser Ansatz ist das RAW-Format, welches im nächsten Kapitel thematisiert wird. 4.2.4 RAW-Format Zu Beginn wird ein Format definiert, das grundlegend einen trennzeichenseparierten String darstellt und als RAW-Format bezeichnet wird. Das RAW-Format wird für die Übertragung von großen Datenmengen (nur relevant für Gitterdaten) verwendet. Hier liegt der Fokus auf der kompakten Übertragung der Daten und nicht wie bei JSON auf einem gut lesbaren bzw. (de)serialisierbaren Format. Für alle anderen Nachrichten wird weiterhin JSON verwendet. Bild 4.1: Aufbau RAW Objekt 43 Der Grundgedanke des RAW-Formates ist es, so wenig Speicherplatz für die Trennzeichen der Werte zu verwenden wie irgendwie möglich. Ein erster Entwurf ist in Bild 4.1 zu sehen. Das RAW-Format ist eine trennzeichenseparierte Zeichenkette und beginnt mit der Zeichenkette RAW“ (Bild 4.1 auf Seite 43). Es wird durch die verschiedenen Trennzeichen in ” drei Ebenen unterteilt. |||“ trennt Ebenen null und eins, ||“ Ebenen eins und zwei und |“ ” ” ” Ebenen zwei und drei. Auf Ebene null definiert der erste String (nach RAW“) den Typ des ” Objekts und enthält weitere Informationen zum Objekt (S). Anschließend folgen dann die Dreiecke des Gitters (D1 − Dn ). Ebene eins wird ausschließlich für die Dreiecke verwendet und trennt die einzelnen Punkte der Dreiecke voneinander (P1 , P2 , P3 ). In Ebene zwei werden die Werte für die Punkte (X-, Y-, Z-Koordinate, Farbwert-Rot, -Grün, -Blau, Wert für die Transparenz und Wert der Lösung) angegeben, ebenfalls aber auch die Informationen zum Typ (siehe erster String). Die Größe der Antworten für die verschieden Dateien sind in Tabelle 4.2 dargestellt (Optimierung: B). Tabelle 4.2: Größe der Antworten der verschieden Dateien Optimierung [MB] Anzahl Elemente Ohne A (45%) B (38%) C (28%) D (27%) Dateiname shaft.vol 2.624 3,8 1,7 1,44 1,01 1,01 20.992 28,7 13,1 11,11 8,27 8,08 shaft 20k.vol shaft 167k.vol 167.936 228,1 103,1 88,01 64,51 63,11 shaft 1.3m.vol 1.343.488 1840,6 831,8 704,99 512,85 501,88 Die Werte in kusiv sind basierend auf den anderen Werten berechnet. Die % Zahl im Header bezieht sich jeweils auf die Optimierungsstufe “Ohne“ Der nächste Schritt besteht darin die Genauigkeit der zu übertragenden Werte zu reduzieren. Hierfür wird von boost::lexical cast<std::string>() auf std::to string() gewechselt (siehe: Tabelle 4.2 Optimierung: C). lexical cast<>() ist eine Funktion aus der C++ Bibliothek Boost (siehe: [BOOS2014]). std::to:string ist in der Header-Datei string definiert. Beide Funktionen konvertieren eine Zahl in eine Zeichenkette (String). lexical cast verwendet dabei eine höhere Genauigkeit als std::to string. Um zu überprüfen, ob dieser Schritt möglich ist wird zunächst ein 3D-Modell mit der höherer Genauigkeit geladen (lexica cast) und anschließend mit eines der niedrigeren Genauigkeit (std::to string). Diese beiden Ansichten werden danach auf optische Unterschiede überprüft. Bei dieser Überprüfung stellt sich heraus, dass es keinen Unterschied in der Darstellung gibt, der auf die veränderte Genauigkeit zurückzuführen ist. Mit dem Ziel die Größe noch weiter zu verringern, werden die Trennzeichen wie folgt ersetzt: 44 • ||| → | • || → < • || → > Dies führt zur letzten Optimierungsstufe (siehe: Tabelle 4.2 auf Seite 44 Optimierung: D). Es ist anzumerken, dass es auch in der letzten Optimierungsstufe nicht möglich ist, das Gitter mit 1,3 Millionen Elemente zu übertragen. Bereits nach 50% der Daten stürzt der Browser bzw. das Browser-Tab ab. Getestet wird in Google Chrome (Version 35.0.1870.2 ” dev-m“). Mit dieser letzten Optimierungsstufe ist das Entwerfen, Optimieren und Implementieren des Datenformats abgeschlossen, und es kann damit begonnen werden die Visualisierung der Daten in WebGL umzusetzen. Weitere theoretische Möglichkeiten zur Optimierung der Übertragung sind in Kapitel 4.4 auf Seite 76 zu finden. 4.2.5 Perspektivische und Orthographische Ansichten Bevor die Implementierung von WebGL durchgeführt werden kann, muss erst einmal die Art der Kameraansicht festgelegt werden. Es stehen die orthographische und perspektivische Ansichten zur Auswahl. Bei der orthographischen Ansicht verändert sich die Größe der Objekte nicht, wenn diese weiter entfernt von der Kamera sind. Diese Ansicht ist für den Menschen eher ungewohnt. Bei der perspektivischen Ansicht hingegen werden Elemente, die weiter von der Kamera entfernt sind, kleiner dargestellt. An diese Ansicht ist das menschliche Auge gewöhnt. Bild 4.2 zeigt einen Vergleich der beiden Ansichten. Bild 4.2: Perspektivische und Orthographische Ansichten Quelle: [NH3D2011] 45 4.2.6 Reines WebGL vs Three.js Die erste Version der Umsetzung der Darstellung des 3D-Modells wird in reinem WebGL implementiert. Bei dieser Art der Implementierung stehen nur die Standardfunktionen der WebGL API zur Verfügung, während alle anderen Funktionen selbst implementiert werden müssen. Bei Three.js hingegen gibt es für die meisten Anwendungsfälle bereits eine Funktion oder Helfer-Klasse. Um den Unterschied zwischen der Implementierung des Projekts in reinem WebGL im Vergleich zu der Implementierung in Three.js etwas klarer zu machen, wird hier ein Beispiel in beiden Varianten implementiert. Das Beispiel ist ein einfaches weißes Rechteck auf schwarzem Hintergrund mit der Höhe und Breite eins. Begonnen wird mit der Implementierung in reinem WebGL. Zuerst muss die Zeichen Umgebung von einem HTML5 Canvas Element abgefragt werden. In dieser Umgebung wird dann der WebGL-Inhalt gerendert. Beim Vorgang des renderns werden die vom Entwickler generierten 3D-Objekte gezeichnet und für den Benutzer dargestellt. Listing 4.5 zeigt, wie es möglich ist die Zeichen-Umgebung in JavaScript abzufragen. Listing 4.5: WebGL-Kontext abfragen [PARI2012, S. 10] 1 f u n c t i o n initWebGL ( c a n v a s ) { 2 var gl ; 3 try 4 { g l = c a n v a s . g e t C o n t e x t ( ” e x p e r i m e n t a l −w e b g l ” ) ; 5 6 } 7 catch ( e ) 8 { v a r msg = ” F e h l e r beim a b f r a g e n d e r WebGL−Umgebung ! : ” 9 10 + e . toString (); 11 thr ow E r r o r ( msg ) ; 12 } 13 return g l ; 14 } Nachdem die Zeichen-Umgebung abgefragt ist, kann der Viewport initialisiert werden. Dieser ist der Bereich des Canvas, in dem gezeichnet werden soll. Um den Viewport zu setzen, muss nur die viewport()-Methode aufgerufen werden (Siehe: Listing 4.6 auf Seite 47). 46 Listing 4.6: Viewport initialisieren [PARI2012, S. 11] 1 function i n i t V i e w p o r t ( gl , canvas ) { g l . v i e w p o r t ( 0 , 0 , c a n v a s . width , c a n v a s . h e i g h t ) ; 2 3 } In diesem Beispiel wird der Viewport auf die komplette Höhe und Breite des Canvas gesetzt. Nach der Initialisierung des Viewports ist die Umgebung bereit zum Zeichnen. Das Zeichnen in WebGL wird durch einfache grafische Primitive umgesetzt (weitere Informationen zu grafischen Primitiven siehe: [SCHA2009, S. 21ff] . Es gibt Dreiecke, Punkte und Linien. Die Position der Eckpunkte dieser Primitive werden in Datenarrays, genannt Buffer, abgelegt. Eine Funktion, um ein Quadrat mit der Breite und Höhe eins und dem Mittelpunkt im Nullpunkt zu zeichnen, ist in Listing 4.7 dargestellt. Listing 4.7: Buffer mit Eckpunkten für ein Quadrat befüllen [PARI2012, S. 12] 1 function createSquare ( gl ) { 2 var vertexBuffer ; 3 vertexBuffer = gl . createBuffer (); 4 g l . b i n d B u f f e r ( g l . ARRAY BUFFER , v e r t e x B u f f e r ) ; 5 var v e r t s = [ 6 0.5 , 0.5 , 0.0 , 7 −0.5 , 0.5 , 0.0 , 0 . 5 , −0.5 , 0 . 0 , 8 −0.5 , −0.5 , 0 . 0 9 10 ]; 11 g l . b u f f e r D a t a ( g l . ARRAY BUFFER , new F l o a t 3 2 A r r a y ( v e r t s ) , 12 13 14 15 g l . STATIC DRAW ) ; var square = { b u f f e r : vertexBuffer , v e r t S i z e : 3 , nVerts : 4 , p r i m t y p e : g l . TRIANGLE STRIP } ; return square ; 16 } Die Funktion gibt ein Objekt zurück, das alle relevanten Informationen über das Quadrat enthält: Buffer mit den Daten (buffer), Anzahl der Eckpunkte (nVerts), Anzahl der Werte pro Punkt (vertSize) und die Art des Objekts (primtype). Bevor das Quadrat gezeichnet werden kann, müssen noch zwei wichtige Matrizen definiert werden. Als erstes die modelViewMatrix, welche die Position des Quadrates relativ zur Kamera festlegt. In diesem Beispiel ist das Quadrat -3,33 Einheiten in Z-Richtung verschoben. Die zweite ist 47 die projectionMatrix. Diese wird vom Shader benötigt, um die 3D- in 2D-Koordinaten zu konvertieren. Listing 4.8 zeigt diese beiden Matrizen. Listing 4.8: Initialisieren der Projection- und ModelView Matrix [PARI2012, S. 13] 1 function i n i t M a t r i c e s () { 2 // M a t r i x f ü r d a s Quadrat t r a n s f o r m i e r e n − v e r s c h i e b e n i n Z 3 // f ü r d i e Kamera 4 m o d e l V i e w M a t r i x = new F l o a t 3 2 A r r a y ( 5 [1 , 0 , 0 , 0 , 6 0, 1, 0, 0, 7 0, 0, 1, 0, 8 0 , 0 , −3.33 , 1 ] 9 ); 10 11 // Die p r o j e c t i o n M a t r i x ( 4 5 d e g r e e f i e l d o f view ) 12 p r o j e c t i o n M a t r i x = new F l o a t 3 2 A r r a y ( 13 [2.41421 , 0 , 0 , 0 , 14 0 , 2.21421 , 0 , 0 , 15 0 , 0 , −1.002002 , −1, 16 0 , 0 , −0.2002002 , 0 ] 17 ); 18 } Zuletzt müssen noch die Shader definiert werden. Shader sind kleine Programme, die in einer C-ähnlichen Sprache (OpenGL Shading Language (siehe: [KHRO2014b])) geschrieben sind und festlegen, wie die Pixel für ein Objekt gezeichnet werden. Ein Shader besteht typischerweise aus zwei Teilen: dem Vertex-Shader und dem Fragment- bzw. PixelShader. Der Vertex-Shader transformiert die Eckpunkt-Koordinaten in 2D-BildschirmKoordinaten. Der Fragment-Shader hingegen ist zuständig für die Farbe der einzelnen Pixel der transformierten Eckpunkte. Dabei werden Aspekte wie Farbe, Textur, Licht und Material mit einbezogen. Der Vertex-Shader in diesem Beispiel kombiniert nur die modelViewMatrix und projectionMatrix, um die finale Position für jeden Eckpunkt zu generieren. Im Fragment-Shader wird jedem Punkt die feste Farbe Weiß zugewiesen (Listing 4.9 auf Seite 49). 48 Listing 4.9: Vertex- und Fragment-Shader [PARI2012, S. 14] 1 var vertexShaderSource = 2 ” a t t r i b u t e vec3 vertexPos ; \ n” + 3 ” u n i f o r m mat4 m o d e l V i e w M a t r i x ; \ n ” + 4 ” u n i f o r m mat4 p r o j e c t i o n M a t r i x ; \ n ” + 5 ” v o i d main ( v o i d ) { \n ” + 6 ” // R e t u r n t h e t r a n f o r m e d and p r o j e c t e d v e r t e x v a l u e \n ” + 7 ” g l P o s i t i o n = r o j e c t i o n M a t r i x ∗ m o d e l V i e w M a t r i x ∗ \n ” + 8 ” 9 ” vec4 ( vertexPos , 1 . 0 ) ; \ n” + }\ n ” ; 10 11 var fragmentShaderSource = v o i d m a i n t ( v o i d ) {\ n ” + 12 ” 13 ” // R e t u r n t h e p i x e l c o l o r : a l w a y o u t p u t white \n ” + 14 ” g l F r a g C o l o r = vec4 ( 1 . 0 , 1 . 0 , 1 . 0 , 1 . 0 ) ; \ n” + 15 ” }\ n ” ; Nachdem die Shader erstellt sind, kann mit dem Zeichnen begonnen werden, welches in der Funktion draw() implementiert ist (Listing 4.10). Zuerst wird das komplette Canvas geleert, indem es mit schwarzer Farbe gefüllt wird (clearColor()). Anschließend wird der Buffer mit dem Quadrat angebunden (bindBuffer()), das Shader Programm geladen (useProgram()) und der Vertex-Shader mit den Matrizen verbunden (vertexAttribPointer() und uniformMatrix4fv()). Abschließend wird drawArrays() aufgerufen, um das Quadrat zu zeichnen. Das Ergebnis kann in Bild 4.3 auf Seite 50 eingesehen werden (vgl. [PARI2012, S. 9ff]). Listing 4.10: Das Quadrat zeichnen [PARI2012, S. 14f] 1 f u n c t i o n draw ( g l , o b j ) { 2 // H i n t e r g r u n d m i t d e r F a r b e Wei ß 3 gl . clearColor (0.0 , 0.0 , 0.0 , 1.0); 4 g l . c l e a r ( g l . COLOR BUFFER BIT ) ; f ü l l e n 5 6 // Z e i c h n e n im V e r t e x −B u f f e r a k t i v i e r e n 7 g l . b i n d B u f f e r ( g l . ARRAY BUFFER , o b j . b u f f e r ) ; 8 9 10 // S h a d e r l a d e n g l . useProgramm ( shaderProgramm ) ; 49 11 12 // E i n s t e l l u n g e n d e r S h a d e r m i t d e r 13 // p r o j e c t i o n / model M a r t r i c e s v e r b i n d e n 14 gl . vertexAttribPointer ( shaderVertexPositionAttribute , 15 o b j . v e r t S i z e , g l . FLOAT , f a l s e , 0 , 0 ) ; 16 17 18 19 gl . uniformMatrix4fv ( shaderProjectionMatrixUniform , false , projectionMatrix ); g l . uniformMatrix4fv ( shaderModelViewMatrixUniform , f a l s e , modelViewMatrix ) ; 20 21 // O b j e k t z e i c h n e n 22 g l . drawArrays ( obj . primtype , 0 , obj . nVerts ) ; 23 } Bild 4.3: Quadrat WebGL / Three.js Im Vergleich dazu folgt die Implementierung für das gleiche Quadrat in Three.js (Listing 4.11 auf Seite 51). Alle Objekte aus Three.js die für die Umsetzung relevant sind, werden im Folgenden erklärt. Der Raum, in dem alles platziert wird, heißt Scene und wird durch ein THREE.Scene Objekt umgesetzt (siehe: [CABE2014a, Kapitel Scene]). Um in diese Scene hineinschauen zu können, wird eine Kamera (THREE.PerspectiveCamera oder THREE.OrthographicCamera) benötigt (siehe: [CABE2014a, Kapitel PerspectiveCamera und OrthographicCamera]). Die Gitterstrukturen werden durch eine THREE.Geometry abgebildet, die Oberfläche für diese Gitterstrukturen (Farben der Punkte, Transparenz) durch ein THREE.Material (siehe: [CABE2014a, Kapitel Geometry und Material]). Von THREE.- 50 Geometry und THREE.Material gibt es noch diverse Unterklassen. Diese werden, falls sie Verwendung finden, später erklärt (für THREE.MeshBasicMaterial und THREE.ShaderMaterial siehe Kapitel 4.2.7 auf Seite 53). Um das 3D-Objekt in der Scene darstellen zu können, wird aus diesen beiden Komponenten ein THREE.Mesh generiert, welches dann das fertige 3D-Objekt darstellt und der Scene hinzugefügt werden kann (siehe: [CABE2014a, Kapitel Mesh]). Für die Beleuchtung bzw. die Lichtquellen wird ein THREE.Light verwendet (siehe: [CABE2014a, Kapitel Light]). In diesem Projekt kommt ausschließlich ein THREE.PointLight zum Einsatz (siehe: [CABE2014a, Kapitel PointLight]). Um die komplette Scene mit der Kamera im Browser darstellen zu können, muss diese noch gerendert werden. Dafür wird ein THREE.WebGLRenderer gebraucht (siehe: [CABE2014a, Kapitel WebGLRenderer]). Listing 4.11: Ein Quadrat zeichnen mit Three.js [PARI2012, S. 20f] 1 <!DOCTYPE html> 2 <html> 3 <head> 4 < t i t l e >A S i m p l e Three . j s Page</ t i t l e > 5 < s c r i p t s r c = ” . . / l i b s / Three . j s ”></ s c r i p t > 6 <s c r i p t > 7 f u n c t i o n onLoad ( ) { 8 // C o n t a i n e r d i v a b f r a g e n 9 v a r c o n t a i n e r = document . g e t E l e m e n t B y I d ( ” c o n t a i n e r ” ) ; 10 11 // Three . j s Render e r s t e l l e n und dem d i v z u w e i s e n 12 v a r r e n d e r e r = new THREE . WebGLRenderer ( ) ; 13 renderer . setSize ( container . offsetWidth , 14 container . offsetHeight ); 15 c o n t a i n e r . a p p e n d C h i l d ( r e n d e r e r . domElement ) ; 16 17 // Three . j s S z e n e g e n e r i e r e n 18 v a r s c e n e = new THREE . Scene ( ) ; 19 20 // Kamera g e n e r i e r e n und d e r S z e n e h i n z u f ü gen 21 v a r camera = new THREE . P e r s p e c t i v e C a m e r a ( 4 5 , 22 contailer . offsetWidth / container . offsetHeight , 23 1 , 4000); 51 24 camera . p o s i t i o n . s e t ( 0 , 0 , 3 . 3 3 ) ; 25 s c e n e . add ( camera ) ; 26 27 // R e c h t e c k e r s t e l l e n und d e r S c z e n e h i n z u f ü gen 28 v a r g e o m e t r y = new THREE . P l a n e G e o m e t r y ( 1 , 1 ) ; 29 v a r mesh = new THREE . Mesh ( geometry , new THREE . M e s h B a s i c M a t e r i a l ( ) ) ; 30 s c e n e . add ( mesh ) ; 31 32 33 // Rendern 34 r e n d e r e r . r e n d e r ( s c e n e , camera ) ; 35 36 } </ s c r i p t > 37 </head> 38 <body onLoad=”onLoad ();” > 39 40 <d i v i d =” c o n t a i n e r ” s t y l e =”w i d t h : 5 0 0 px ; h e i g h t : 5 0 0 px;”></ d i v > </body> 41 </html> Auch hier kann das Ergebnis in Bild 4.3 auf Seite 50 angesehen werden. Zunächst wird die Anzahl der benötigten Code-Zeilen verglichen. In reinem WebGL werden 88 Zeilen benötigt, wobei noch der komplette HTML-Teil der Seite fehlt. Vergleicht man das mit dem Three.js Code, der nur 27 Zeilen (39 Zeilen mit HTML-Anteil) benötigt, stellt man fest, dass mit Three.js gerade mal ein Drittel des Codes benötigt wird. Außerdem sieht der Code deutlich übersichtlicher und gepflegter aus. Das führt dazu, dass die Wartbarkeit des Codes deutlich zunimmt, ebenso wird es einfacher, mit mehreren Entwicklern an einem Projekt zu arbeiten. Während bei reinem WebGL noch die Matrizen aufgestellt und miteinander multipliziert werden mussten, passiert das bei Three.js alles intern und der Entwickler muss sich darum nicht kümmern. Das führt auch dazu, dass Bibliotheken wie glMatrix (siehe: [JONE2014]) nicht extra eingebunden werden müssen. glMatrix ist eine JavaScript Bibliothek für die mathematischen Operationen mit Matrizen und Vektoren, die auf Geschwindigkeit optimiert ist. Weitergehend muss keine eigene Funktion drawScene() generiert werden, in der alle Operationen an der Scene (Rotation, Translation) durchgeführt werden müssen. Wiederum führt dies dazu, dass der Entwickler viel weniger Wissen im Bereich der 3D-Mathematik haben muss, wenn er Three.js verwendet als bei der Nutzung von WebGL. Ein weiterer 52 wichtiger Aspekt ist, dass in Three.js das Selektieren von Elementen (Picking) bereits unterstützt wird und es in WebGL nur mit einem großen (Arbeits-) Aufwand möglich ist, diese Funktion selbst zu implementieren. Weitere Informationen zum Selektieren von Elementen sind in Kapitel 4.2.8 auf Seite 59 zu finden. Auch hier wäre wieder ein sehr gutes Verständnis im Bereich der 3D-Mathematik erforderlich (vgl. [GREG2012]). Zusammengefasst hat es nur Vorteile Three.js anstelle von WebGL zu verwenden. 4.2.7 MeshBasicMaterial, ShaderMaterial und Transparenz Für die Darstellung der Dreiecke des 3D-Objekts wird eine THREE.Geometry verwendet. Dieses Objekt beinhaltet alle relevanten Informationen für die Struktur eines 3D-Objekts. Würde man dieses Objekt visualisieren, wäre es das 3D-Objekt als Gitter-Struktur. Jede Geometry beinhaltet eine id zur Identifikation, einen Array vertices für die Punkte, ein Array colors für die Farben pro Punkt und ein Array faces für die Dreiecke. Es gibt noch diverse andere Eigenschaften, diese sind jedoch für die Umsetzung nicht relevant. Eine komplette Liste der Eigenschaften kann in der Quelle nachgelesen werden. Neben der THREE.Geometry wird noch ein THREE.Material benötigt. Mit dem Material wird die (Ober-) Fläche der Geometry und deren Eigenschaften beschrieben. Für die erste Version der Umsetzung wird ein THREE.MeshBasicMaterial gewählt (siehe: [CABE2014a, Kapitel MeshBasicMaterial]). Die relevante Eigenschaft dieses Materials ist vertexColors. Mit dieser wird dem Material mitgeteilt, dass für die Farben der Dreiecke (faces) die Farben der Punkte (vertices) verwendet werden sollen. Der Wert dafür ist THREE.VertexColors. 1 v a r m a t e r i a l = new THREE . M e s h B a s i c M a t e r i a l ( { v e r t e x C o l o r s : THREE . V e r t e x C o l o r s } 2 3 ); THREE.Geometry und THREE.MeshBasicMaterial sind eigenständige Objekte. Damit diese gezeichnet werden können, müssen sie in einem THREE.Mesh zusammengefasst und abschließend der Scene hinzugefügt werden. Ein triviales Beispiel ist im folgenden Listing zu sehen. Anstelle einer THREE.Geometry wird hier eine THREE.CubeGeometry verwendet, um das Beispiel möglichst einfach und kurz zu halten. Three.CubeGeometry(1, 1, 1) erstellt einen Würfel mit Kantenlänge eins. THREE.MeshBasicMaterial({ color: 0x00FF00 }) erstellt ein Material mit der Farbe Grün (0x00FF00). 1 v a r g e o m e t r y = new THREE . CubeGeometry ( 1 , 1 , 1 ) ; 2 v a r m a t e r i a l = new THREE . M e s h B a s i c M a t e r i a l ( { c o l o r : 0 x00FF00 } ) ; 3 v a r mesh = new THREE . Mesh ( geometry , m a t e r i a l ) ; 53 4 s c e n e . add ( mesh ) ; Das THREE.MeshBasicMaterial erfüllt soweit seinen Zweck. Es muss, basierend auf den Anforderungen, möglich sein Elemente transparent zu schalten. Zum Einen wird diese Funktionalität benötigt, um nur Lösungswerte in einem bestimmten Bereich anzuzeigen. Hier könnte es sein, dass bei einer beispielhaften Werteskala von 0 bis 42 nur die Werte von 37 bis 42 relevant sind. Weitergehend muss es auch möglich sein, Elemente auszublenden, die die Sicht auf andere Elemente versperren. Der Benutzer könnte sich für den Farbverlauf im Inneren des Modells interessieren und dieser ist nur schwer anzusehen, wenn alle Elemente vom Inneren zum Rand auch sichtbar sind. Leider unterstützt die Implementierung des Farb-Objekts THREE.Color nur die Werte Rot, Grün und Blau (RGB) und keinen zusätzlichen Wert für Transparenz/Alpha (RGBA) (siehe: [CABE2014a, Kapitel Color]). Das THREE.MeshBasicMaterial hat die Eigenschaften transparency und opacity. Wenn man die Eigenschaft transparency auf true (aktiviert) und opacity auf einen Wert zwischen 1,0 (komplett sichtbar) und 0,0 (komplett ausgeblendet) setzt, wird das Material transparent. Diese Möglichkeit, die Transparenz zu aktivieren, ist sehr einfach. Leider ist der Nachteil dieser Umsetzung, dass der opacity-Wert für das komplette THREE.MeshBasicMaterial gilt. Es ist also nicht möglich, einzelne Dreiecke oder Punkte auf transparent zu schalten und den Rest unverändert zu lassen. Eine erste Lösung für dieses Problem ist, nicht das komplette 3D-Modell als ein THREE.Mesh zu erzeugen sondern als N-teiliges THREE.Mesh, wobei N die Anzahl der Dreiecke des 3D-Modells ist. Der Vorteil hierbei liegt darin, dass man jetzt für jedes Dreieck den opacity-Wert unabhängig von den anderen einstellen kann. Leider überwiegen auch hier die Nachteile. Es kann noch immer kein opacity-Wert für jeden Punkt gesetzt werden (nur für jedes Dreieck). Weitergehend ist es ein großer Nachteil die Performance betreffend für jedes Dreieck ein eigenes THREE.Mesh zu generieren. Zum einen führt das bei größeren Modellen zu niedrigeren FPS-Werten (Bilder pro Sekunde) und zum anderen müssen Operationen wie Rotation, Translation oder Skalierung auf N und nicht mehr nur auf ein THREE.Mesh angewendet werden. Es muss eine andere Möglichkeit gefunden werden, um dieses Problem zu lösen. Das THREE.ShaderMaterial ist eine Lösung, die Transparenz für jeden Punkt und die Verwendung von nur einem THREE.Mesh zu ermöglichen (siehe: [CABE2014a, Kapitel ShaderMaterial]). Durch dessen Verwendung hat man die Möglichkeit, entweder weiter die Farben aus dem Array colors der THREE.Geomertry zu verwenden, oder aber die Farben über einen eigenen Shader zu steuern. Letzteres ermöglicht es, das gewünschte Ziel zu 54 erreichen. Hierfür müssen jedoch erst einmal die Shader (Vertex- und Fragment-Shader) definiert werden, die für das THREE.ShaderMaterial benötigt werden. Listing 4.12 und 4.13 zeigen jeweils einen sehr rudimentären Shader, der als Basis verwendet wird. Listing 4.12: Rudimentärer Vertex-Shader 1 < s c r i p t type=”x−s h a d e r /x−v e r t e x ” i d =” v e r t e x s h a d e r ”> 2 v o i d main ( ) 3 { g l P o s i t i o n = p r o j e c t i o n M a t r i x ∗ modelViewMatrix 4 ∗ vec4 ( p o s i t i o n , 1 . 0 ) ; 5 6 } 7 </ s c r i p t > Listing 4.13: Rudimentärer Fragment-Shader 1 < s c r i p t type=”x−s h a d e r /x−f r a g m e n t ” i d =” f r a g m e n t s h a d e r ”> 2 v o i d main ( ) 3 { 4 // S e t t i n g Each P i x e l To Red 5 g l F r a g C o l o r = vec4 ( 1 . 0 , 0.0 , 0.0 , 1 . 0 ) ; 6 } 7 </ s c r i p t > Diese beiden Shader haben noch keine besondere Funktion. Im Vertex-Shader wird die Postion für jedes Pixel bestimmt, indem die ModelViewProjectionMatrix (ModeViewMatrix · ProjectionMatrix) mit dem Vertex (Position des Pixels vor der Transformierung) multipliziert wird. Im Fragment-Shader wird dann jedem Punkt die Farbe Rot zugewiesen. Diese beiden Shader werden anschließend modifiziert. Es werden dem Vertex-Shader (Listing 4.12) die Variablen customColor und customOpacity hinzugefügt. Der Typ vec3 ist ein Vektor der Länge drei und float eine Gleitkommazahl. Das zusätzliche Präfix attribute besagt, dass diese Werte von außen (z.B. aus JavaScript) dem Shader übergeben werden. Die Alternative zum attribute-Präfix ist das uniform-Präfix (findet hier keine Verwendung). Der Unterschied zu diesem ist, dass es nur einen Wert pro Shader gibt und bei attribute einen Wert für jeden Vertex (Punkt/Pixel). Zusätzlich werden die Variablen vColor und vOpacity definiert. Das Präfix varying bedeutet, dass sowohl der Vertex- als auch der Fragment-Shader Zugriff auf diese Variablen haben. Wie beim attribute- gibt es auch beim varying-Präfix einen Wert für jeden Ver- 55 tex. In der Funktion main wird customColor vColor und custoOpacity vOpacity zugewiesen, damit die Werte von customColor und custoOpacity auch im Fragment-Shader zur Verfügung stehen. Der modifizierte Vertex-Shader ist in Listing 4.14 abgebildet. Listing 4.14: Modifizierter Vertex-Shader für das THREE.ShaderMaterial 1 < s c r i p t type=”x−s h a d e r /x−v e r t e x ” i d =” v e r t e x s h a d e r ”> 2 a t t r i b u t e vec3 customColor ; 3 a t t r i b u t e f l o a t customOpacity ; 4 5 v a r y i n g vec3 vColor ; 6 varying f l o a t vOpacity ; 7 8 v o i d main ( ) { vColor = customColor ; 9 10 vOpacity = customOpacity ; 11 g l P o s i t i o n = p r o j e c t i o n M a t r i x ∗ modelViewMatrix ∗ vec4 ( p o s i t i o n , 1 . 0 ) ; 12 13 } 14 </ s c r i p t > Auch dem Fragment-Shader (Listing 4.13 auf Seite 55) werden die Variablen vColor und vOpacity hinzugefügt. Diese werden verwendet, um die Farbe und Transparenz für jeden Punkt zu setzen. Die Farbe gl FragColor ist ein Vektor der Länge vier (vec4) und beinhaltet die drei Farbwerte (Rot, Grün und Blau) und den Transparenz- bzw. Alphawert. Der modifizierte Fragment-Shader ist in Listing 4.15 dargestellt. Listing 4.15: Modifizierter Fragment-Shader für das THREE.ShaderMaterial 1 < s c r i p t type=”x−s h a d e r /x−f r a g m e n t ” i d =” f r a g m e n t s h a d e r ”> 2 v a r y i n g vec3 vColor ; 3 varying f l o a t vOpacity ; 4 5 v o i d main ( ) { g l F r a g C o l o r = vec4 ( vColor , vOpacity ) ; 6 7 } 8 </ s c r i p t > Nachdem die Shader fertiggestellt sind, müssen die attribute Variablen mit dem JavaScript-Code verknüpft werden. Dafür wird zunächst ein Objekt mit dem Namen attribu- 56 tes in JavaScript definiert und die benötigten Parameter zugewiesen (Listing 4.16). Type c“ steht für einen Farbwert (Array mit drei Werten) und f“ für eine Gleitkommazahl ” ” (float). Dem value wird jeweils ein leeres Array zugewiesen. Listing 4.16: Attribute Objekt 1 var a t t r i b u t e s = { 2 c u s t o m C o l o r : { type : ’ c ’ , v a l u e : [ ] } , 3 c u s t o m O p a c i t y : { type : ’ f ’ , v a l u e : [ ] } 4 }; Unter der Verwendung der beiden Shader und des attributes-Objektes kann nun das THREE.ShaderMaterial erstellt werden. Es werden noch einige zusätzliche Parameter gesetzt. blending wird auf THREE.NormalBlending gesetzt. Dieser Wert wird über Try’n’error ermittelt und ist der Werte, bei dem die Farben korrekt dargestellt wurden. depthTest (siehe: [KHRO2013b]) wurde mit true aktiviert. transparent: true aktiviert die Transparenz. side: THREE.DoubleSide gibt an, dass beide Seiten eines Dreiecks gezeichnet werden sollen. Falls dieser Wert nicht gesetzt ist, müssen alle Punkte des Dreiecks in der richtigen Reihenfolge hinzugefügt werden. Die Vorderseite eines Dreiecks wird dann berechnet, indem der Richtungsvektor basierend auf den Punkten des Dreiecks im Uhrzeigersinn bestimmt wird. linewidth: 2 setzt die Breite der Linien auf zwei. Das vollständige shaderMaterial ist in Listing 4.17 abgebildet. Listing 4.17: Initialisierung ShaderMaterial 1 v a r s h a d e r M a t e r i a l = new THREE . S h a d e r M a t e r i a l ( { 2 attributes : attributes , 3 vertexShader : document . g e t E l e m e n t B y I d ( ’ v e r t e x s h a d e r ’ ) . t e x t C o n t e n t , 4 5 fragmentShader : document . g e t E l e m e n t B y I d ( ’ f r a g m e n t s h a d e r ’ ) . t e x t C o n t e n t , 6 7 b l e n d i n g : THREE . N o r m a l B l e n d i n g , 8 depthTest : true , 9 transparent : true , 10 s i d e : THREE . D o u b l e S i d e , 11 linewidth : 2 12 }); Abschließend werden alle Komponenten zusammengeführt. Ein kurzes Beispiel für ein einziges Dreieck ist in Listing 4.18 auf Seite 58 dargestellt. 57 Listing 4.18: Ein einfaches Dreieck mit ShaderMaterial 1 // O b j e k t f ü r d i e S h a d e r A t t r i b u t e e r s t e l l e n 2 var a t t r i b u t e s = { 3 c u s t o m C o l o r : { type : ’ c ’ , v a l u e : [ ] } , 4 c u s t o m O p a c i t y : { type : ’ f ’ , v a l u e : [ ] } 5 }; 6 7 // G e o m e t r i e e r s t e l l e n 8 v a r g e o m e t r y = new THREE . Geometry ( ) ; 9 // Punkt 1 m i t d e r F a r b e Gr ün und ohne T r a n s p a r e n z 10 g e o m e t r y . v e r t i c e s . push ( new THREE . V e c t o r 3 ( 0 . 0 , 0 . 0 , 0 . 0 ) ; 11 a t t r i b u t e s . customColor . value [ 0 ] = 12 13 new THREE . C o l o r (THREE . C o l o r K e y w o r d s . g r e e n ) ; a t t r i b u t e s . customOpacity . value [ 0 ] = 1 . 0 ; 14 15 // Punkt 2 m i t d e r F a r b e Bla u und 34% t r a n s p a r e n t 16 g e o m e t r y . v e r t i c e s . push ( new THREE . V e c t o r 3 ( 1 . 0 , 2 . 0 , 0 . 0 ) ; 17 a t t r i b u t e s . customColor . value [ 1 ] = 18 19 new THREE . C o l o r (THREE . C o l o r K e y w o r d s . b l u e ) ; a t t r i b u t e s . customOpacity . value [ 1 ] = 0 . 6 6 ; 20 21 // Punkt 3 m i t d e r F a r b e Bla u und 67% t r a n s p a r e n t 22 g e o m e t r y . v e r t i c e s . push ( new THREE . V e c t o r 3 ( 2 . 0 , 0 . 0 , 0 . 0 ) ; 23 a t t r i b u t e s . customColor . value [ 2 ] = 24 25 new THREE . C o l o r (THREE . C o l o r K e y w o r d s . r e d ) ; a t t r i b u t e s . customOpacity . value [ 2 ] = 0 . 3 3 ; 26 27 // S h a d e r M a t e r i a l e r s t e l l e n 28 v a r s h a d e r M a t e r i a l = new THREE . S h a d e r M a t e r i a l ( { 29 attributes : attributes , 30 vertexShader : 31 32 33 document . g e t E l e m e n t B y I d ( ’ v e r t e x s h a d e r ’ ) . t e x t C o n t e n t , fragmentShader : document . g e t E l e m e n t B y I d ( ’ f r a g m e n t s h a d e r ’ ) . t e x t C o n t e n t , 34 b l e n d i n g : THREE . N o r m a l B l e n d i n g , 35 depthTest : true , 58 36 transparent : true , 37 s i d e : THREE . D o u b l e S i d e , 38 linewidth : 2 39 }); 40 // G e o m e t r i e und M a t e r i a l zusammenf ü h r e n 41 v a r mesh = new THREE . Mesh ( geometry , m a t e r i a l ) ; 42 // Mesh d e r Scene h i n z u f ü gen 43 s c e n e . add ( mesh ) ; Über die Variable attributes können die Werte für die Farbe und Transparenz eines jeden Punktes einzeln geändert werden. Weitere Informationen zur Transparenz und deren Nutzen sind in Kapitel 4.2.9 auf Seite 65 zu finden. 4.2.8 Auswahl von Elementen Für das Auswählen von Elementen wird der THREE.Raycaster verwendet (siehe: [CABE2014a, Kapitel Raycaster]). Dieser ist ein Helfer-Objekt, das beim Auswählen von Elementen in Three.js unterstützt. Als Parameter benötigt der THREE.Raycaster die Position der Kamera (THREE.Camera), welche den Ursprung repräsentiert. Außerdem wird noch ein Vektor benötigt, der die Blickrichtung darstellt und ein THREE.Projector (siehe: [CABE2014a, Kapitel Projector]). Weitere Informationen zur Unprojekt-Funktion und somit der Funktionsweise des Raycaster [GHOS2010]. 1 f u n c t i o n g e t C l i c k e d O b j e c t s ( mouseX , mouseY ) { 2 v a r v e c t o r = new THREE . V e c t o r 3 ( mouseX , mouseY , . 5 ) ; 3 p r o j e c t o r . u n p r o j e c t V e c t o r ( v e c t o r , camera ) ; 4 5 v e c t o r = v e c t o r . sub ( camera . p o s i t i o n ) . n o r m a l i z e ( ) 6 v a r r a y c a s t e r = new THREE . R a y c a s t e r ( camera . p o s i t i o n , v e c t o r ) ; 7 var i n t e r s e c t s = 8 9 10 r a y c a s t e r . i n t e r s e c t O b j e c t s ( scene . children , f a l s e ) ; ... return i n t e r s e c t s ; 11 } intersects enthält ein Array mit Elementen, die der Benutzer ausgewählt haben könnte. Diese Elemente sind der Entfernung von der Kamera nach geordnet. Da aber nur Elemente vom Typ THREE.Mesh relevant sind, muss das Array noch bereinigt werden. Der folgende 59 Code-Ausschnitt zeigt die while-Schleife zur Bereinigung des Arrays. Diese gehört an die Stelle ...“ (Zeile 9) vom vorhergehenden Listing. ” 1 w h i l e ( ! ( i n t e r s e c t s [ 0 ] . o b j e c t i n s t a n c e o f THREE . Mesh ) ) { i n t e r s e c t s . remove ( 0 ) ; 2 3 } Die einzelnen Elemente des Arrays, das zurückgegeben wird, haben immer den gleichen Aufbau. Dieser wird im folgenden Listing dargestellt. 1 { 2 d i s t a n c e : 108.18926694699213 , 3 p o i n t : THREE . V e c t o r 3 , 4 f a c e : THREE . Face3 , 5 f a c e I n d e x : 1399 , 6 o b j e c t : THREE . Mesh 7 } distance gibt die Entfernung zur Kamera an und point den Punkt, an dem das Dreieck geschnitten wird. face beinhaltet alle Informationen zu dem ausgewählten Dreieck, wobei faceIndex der Index des ausgewählten Dreiecks ist. object stellt das THREE.Mesh dar, zu dem das Dreieck gehört. Diese Informationen werden als Grundlage für das Selektieren verwendet. Es kann damit herausgefunden werden, welches Dreieck vom Benutzer ausgewählt wird. Als nächstes muss eine Möglichkeit geschaffen werden, dem Benutzer eine visuelle Bestätigung seiner Auswahl zu geben. Der erste Ansatz ist, die Farbe des ausgewählten Dreiecks auf Blau zu ändern. Ein Mesh mit ausgewählten Dreiecken ist in Bild 4.4 auf Seite 61 abgebildet. Zu den Informationen vom Dreieck in der Variable face gehören auch die Indizes der drei Punkte (face.a, face.b und face.c). Unter Verwendung dieser können die Punkte in dem Array für die Farben (attributes.customColor.value) gefunden und geändert werden. 1 2 3 4 5 6 7 60 a t t r i b u t e s . customColor . value [ face . a ] = new THREE . C o l o r (THREE . C o l o r K e y w o r d s . b l u e ) ; a t t r i b u t e s . customColor . value [ face . b ] = new THREE . C o l o r (THREE . C o l o r K e y w o r d s . b l u e ) ; a t t r i b u t e s . customColor . value [ face . c ] = new THREE . C o l o r (THREE . C o l o r K e y w o r d s . b l u e ) ; a t t r i b u t e s . customColor . needsUpdate = t r u e ; Bild 4.4: Mesh mit ausgewählten Dreiecken Auch hier muss mit needsUpdate signalisiert werden, dass sich etwas in dem Array geändert hat. Es werden somit alle vom Benutzer ausgewählten Dreiecke in der Farbe Blau markiert. Damit wird allerdings auch das nächste Problem deutlich: Welche Farbe nimmt das Dreieck an, wenn der Benutzer die Markierung aufhebt? Für den Fall, dass keine Lösung vorhanden ist, ist der Fall klar. Es wird die Farbe einfach wieder auf den Standardwert (Grau) zurückgesetzt. Was ist aber, wenn eine Lösung vorhanden ist? Es muss eine Möglichkeit gefunden werden, die Farbe, die durch das Blau überschrieben wurde, wiederherzustellen. Dafür wird die folgende Lösung entwickelt: Die Farben müssen gesichert werden, bevor diese durch das Blau überschrieben werden. Da aber nicht nur die Farben, sondern auch die Transparenz mit dem Selektieren überschrieben wird, müssen diese Werte ebenso gesichert werden. Als Ort für die Sicherung der Werte werden zwei Arrays definiert. selectedElements beinhaltet die Id des gesicherten Elements und selectedElementsBackup die kompletten Werte. Der Aufbau des Objektes für die Datensicherung sieht wie folgt aus: 61 1 var values = { 2 opacity : { 3 a : getCloneOfObject ( a t t r i b u t e s . customOpacity . value [ item . a ] ) , 4 b : getCloneOfObject ( a t t r i b u t e s . customOpacity . value [ item . b ] ) , 5 c : getCloneOfObject ( a t t r i b u t e s . customOpacity . value [ item . c ] ) 6 }, 7 color : { 8 a : getCloneOfObject ( a t t r i b u t e s . customColor . value [ item . a ] ) , 9 b : getCloneOfObject ( a t t r i b u t e s . customColor . value [ item . b ] ) , 10 c : getCloneOfObject ( a t t r i b u t e s . customColor . value [ item . c ] ) 11 }, 12 indices : { 13 a : getCloneOfObject ( item . a ) , 14 b : getCloneOfObject ( item . b ) , 15 c : getCloneOfObject ( item . c ) } 16 17 }; item ist hierbei das zu sichernde Objekt. getCloneOfObject() erstellt eine tiefe Kopie von einem Objekt, was bedeutet, dass eine Kopie des Objekts erstellt wird und dieses keine Verbindung mehr zum Quellobjekt hat. Das Gegenteil ist eine flache Kopie. Bei dieser Art der Kopie ist noch eine Verbindung zum Quellobjekt vorhanden. Dieses könnte man erreichen können mit: 1 a : item . a Wird bei einer flachen Kopie etwas im Quellobjekt geändert, wird diese Änderung auch im kopierten Objekt durchgeführt. Das gleiche gilt auch in die andere Richtung. Bei einer tiefen Kopie hingegen ist das nicht der Fall. Dort können Änderungen in den Objekten gemacht werden, ohne dass das andere Objekt mit verändert wird. Da im nächsten Schritt item verändert wird, hätten sich damit auch die Werte in values verändert und die gesicherten Werte wären verloren gegangen, weshalb eine tiefe und keine flache Kopie benötigt wird. Als erstes werden die item.id und values in die dafür vorgesehenen Arrays eingefügt und anschließend diese Werte geändert. 1 s e l e c t e d E l e m e n t s B a c k u p . push ( g e t C l o n e O f O b j e c t ( v a l u e s ) ) ; 2 s e l e c t e d E l e m e n t s . push ( i t e m . i d ) ; 3 4 62 a t t r i b u t e s . customOpacity . value [ item . a ] = 1 . 0 ; 5 a t t r i b u t e s . customOpacity . value [ item . b ] = 1 . 0 ; 6 a t t r i b u t e s . customOpacity . value [ item . c ] = 1 . 0 ; 7 a t t r i b u t e s . customOpacity . needsUpdate = t r u e ; 8 9 10 11 12 13 14 15 a t t r i b u t e s . customColor . value [ item . a ] = new THREE . C o l o r (THREE . C o l o r K e y w o r d s . l i g h t b l u e ) ; a t t r i b u t e s . customColor . value [ item . b ] = new THREE . C o l o r (THREE . C o l o r K e y w o r d s . l i g h t b l u e ) ; a t t r i b u t e s . customColor . value [ item . c ] = new THREE . C o l o r (THREE . C o l o r K e y w o r d s . l i g h t b l u e ) ; a t t r i b u t e s . customColor . needsUpdate = t r u e ; Es ist nun möglich, Elemente zu selektieren und dies dem Benutzer mit der Änderung der Farbe auf Blau zu signalisieren. Elemente müssen jedoch nicht nur selektiert, sondern auch deselektiert werden können. Wie erkennt man, dass ein Punkt bereits selektiert ist? Es ist, wie vorher behandelt, an der blauen Farbe zu erkennen, ob ein Punkt bereits selektiert ist oder nicht. Jedoch was bedeutet es für den Code zur Überprüfung, ob ein Dreieck bereits selektiert ist oder nicht? Es müsste für jedes angeklickte Dreieck überprüft werden, ob die Farbe der einzelnen Punkte THREE.ColorKeywords.lightblue entspricht. Es kann allerdings durchaus vorkommen, dass Dreiecke schon vor dem Selektieren die Farbe blau hatten. Die Unterscheidung zwischen von vornherein blauen und selektierten Punkten ist also ohne weitere Informationen nicht möglich. Es werden bereits alle Ids von selektierten Dreiecken in das Array selectedElements eingefügt. Anstelle die Farbe zu überprüfen, kann hier überprüft werden, ob die id bereits in dem Array vorhanden ist. Ist das der Fall, wird das Element deselektiert. Ist das jedoch nicht der Fall, wird das Element selektiert. Dieser zweite Ansatz funktioniert auch, wenn das Element vor dem Selektieren bereits die Farbe Blau hat. Leider muss hierfür immer das komplette Array durchsucht werden. Auch dieser Ansatz wird wieder verworfen. Es wird ein einfacherer und effizienterer Ansatz verfolgt. Statt das komplette Array nach der id zu durchsuchen, wird jedem Element eine zusätzliche Eigenschaft hinzugefügt. selected heißt diese und kann die Zustände true oder false annehmen. Diese Eigenschaft wird dem Element erst hinzugefügt, wenn es das erste Mal selektiert wird. Vorher ist sie nicht vorhanden. Hier wird sich eine JavaScriptFunktionalität zu Nutze gemacht. Wenn mit einer if-Bedingung versucht wird den Wert von einem Objekt oder einer Eigenschaft (hier: selected) abzufragen, dieser aber nicht 63 existiert, wird der Wert als false interpretiert. 1 i f ( item . s e l e c t e d ) 2 { // S p r i n g t h i e r h i n wenn I t e m . s e l e c t e d = t r u e i s t . 3 4 } else { 5 // S p r i n g t h i e r h i n wenn i t e m . s e l e c t e d noch n i c h t v o r h a d e n i s t 6 // o d e r a b e r i t e m . s e l e c t e d = f a l s e i s t . 7 } Es werden alle Elemente nach Abschluss des Selektierens um die Eigenschaft selected erweitert. 1 item . s e l e c t e d = t r u e ; Um ein Element zu deselektieren, müssen die Schritte vom Selektieren umgekehrt werden. Zuerst wird überprüft, ob das Element schon selektiert ist. Dann werden die Werte aus dem Array selectedElementsBackup geladen. Diese werden dem Dreieck dann wieder zugewiesen und abschließend aus dem Array entfernt. 1 2 function d e s e l e c t I t e m ( item ) { i f ( item . s e l e c t e d ) { 3 var i n d e x I d = sc . data . s e l e c t e d E l e m e n t s . indexOf ( item . i d ) ; 4 i f ( sc . data . selectedElementsBackup [ i n d e x I d ] ) { 5 6 7 8 9 10 11 12 13 var values = getCloneOfObject ( selectedElementsBackup [ indexId ] ) ; a t t r i b u t e s . customOpacity . value [ v a l u e s . i n d i c e s . a ] = getCloneOfObject ( values . opacity . a ) ; a t t r i b u t e s . customOpacity . value [ v a l u e s . i n d i c e s . b ] = getCloneOfObject ( values . opacity . b ) ; a t t r i b u t e s . customOpacity . value [ v a l u e s . i n d i c e s . c ] = getCloneOfObject ( values . opacity . c ) ; a t t r i b u t e s . customOpacity . needsUpdate = t r u e ; 14 15 16 17 18 19 20 64 a t t r i b u t e s . customColor . value [ v a l u e s . i n d i c e s . a ] = getCloneOfObject ( values . color . a ) ; a t t r i b u t e s . customColor . value [ v a l u e s . i n d i c e s . b ] = getCloneOfObject ( values . color . b ) ; a t t r i b u t e s . customColor . value [ v a l u e s . i n d i c e s . c ] = getCloneOfObject ( values . color . c ) ; 21 a t t r i b u t e s . customColor . needsUpdate = t r u e ; 22 item . s e l e c t e d = f a l s e ; 23 s e l e c t e d E l e m e n t s B a c k u p . remove ( i n d e x I d ) ; 24 s e l e c t e d E l e m e n t s . remove ( i n d e x I d ) ; } 25 } 26 27 } Nachdem die Werte in attributes.customOpacity aktualisiert sind, muss noch needsUpdate auf true gesetzt werden, um dem Renderer mitzuteilen, dass sich etwas geändert hat. Nachdem es jetzt möglich ist Elemente zu (de)selektieren, wird dieses Kapitel auch abgeschlossen. Es kann damit begonnen werden, Aktionen mit den selektierten Elemente durchzuführen. 4.2.9 Ausblenden von Elementen Wie bereits weiter oben erwähnt sollte es möglich sein, einen bestimmten Wertebereich ein- bzw. auszublenden. Um diese Funktion umsetzen zu können ist es notwendig, dass für jeden Punkt auch auf den Wert der Lösung zugegriffen werden kann. Die Lösungswerte werden von dem Server immer mit der Struktur zusammen an den Client übertragen. Liegt zuerst keine Lösung vor, so wird der Wert standardmäßig mit 0,5 initialisiert. Diese Werte müssen zwischengespeichert werden. Dafür wird im Client ein Array mit dem Namen points generiert, worin alle Punkte abgespeichert werden. Für jeden Punkt werden die Werte: X-, Y-, Z-Koordinate, Id und (Lösungs-) Wert gespeichert. Da die Arrays points und geometry.vertex in der gleichen Funktion befüllt werden, ist der Index bei beiden gleich. Für Vertex vier können also die Werte an Position Nummer vier im points Array gefunden werden. Dieser Umstand macht es möglich die Transparenz, basierend auf dem ausgewählten Wertebereich, zu schalten. Listing 4.19 zeigt eine Funktion zum Aktualisieren der Transparenz für alle Punkte. Diese hat die zwei Parameter minValue und maxValue, welche den Wertebereich eingrenzen. Ein Mesh mit ausgeblendeten Dreiecken wird in Bild 4.5 auf Seite 67 dargestellt. Listing 4.19: Ausblenden von Lösungswerten in einem Bereich 1 2 3 4 f u n c t i o n u p d a t e O p a c i t y ( minValue , maxValue ) { f o r ( v a r i = 0 , p o i n t ; p o i n t = p o i n t s [ i ] ; i ++) { a t t r i b u t e s . customOpacity . value [ i ] = p o i n t . v a l u e < m i n V a l u e | | p o i n t . v a l u e > maxValue ? 0 . 0 : 1 . 0 ; 65 5 } 6 a t t r i b u t e s . customOpacity . needsUpdate = t r u e ; 7 } Das Ausblenden von allen selektierten Elementen funktioniert ähnlich. Es müssen auch nicht wie vorher beim Selektieren die Werte erst gesichert werden, da es nur zwei Zustände gibt. 1 function hideSelectedElements () { 2 f o r ( v a r i = 0 , i d ; i d = s e l e c t e d E l e m e n t s [ i ] ; i ++) { 3 a t t r i b u t e s . customOpacity . v a l u e [ geometry . f a c e s [ i d ] . a ] = 0 . 0 ; 4 a t t r i b u t e s . customOpacity . v a l u e [ geometry . f a c e s [ i d ] . b ] = 0 . 0 ; 5 a t t r i b u t e s . customOpacity . v a l u e [ geometry . f a c e s [ i d ] . c ] = 0 . 0 ; 6 } 7 a t t r i b u t e s . customOpacity . needsUpdate = t r u e ; 8 } Nachdem die Werte in attributes.customOpacity aktualisiert sind, muss needsUpdate auf true gesetzt werden, um dem Renderer mitzuteilen, dass sich etwas geändert hat. Ein weiteres Anwendungsgebiet für das Transparentschalten von Objekten ist das Ausblenden von mehreren Schichten von Dreiecken. Gedacht ist es wie folgt: der Benutzer selektiert einige Dreiecke oder zieht mit der Maus einen Rahmen. Nachdem die Dreiecke ausgewählt sind, können diese über ein Kontext-Menü (Rechts-Klick) ausgeblendet werden. Dies führt dazu, dass der Benutzer ein kleines Stück in das 3D-Modell hereinschauen kann. Eine weitere Einsicht wäre allerdings nicht möglich. Dies ist der Fall, weil die AuswahlFunktion immer das Element mit der kleinsten Entfernung zur Kamera wählt und das immer dem Element direkt am Rand entspricht. Da das allerdings sicherlich nicht den Anforderungen entspricht was das Ausblenden von Elementen angeht, muss hier noch etwas weiter entwickelt werden. Es soll eine Möglichkeit gefunden werden, Elemente so auszublenden, als würde man ein Loch in das 3D-Modell graben. Der erste Schritt dafür ist, jedem Element, ähnlich wie beim Selektieren, eine neue Eigenschaft zuzuweisen. Auch hier wird diese Eigenschaft erst erstellt, wenn das Element das erste Mal ausgeblendet wird. Die Eigenschaft heißt in diesem Fall invisible, wobei hier die invertierte Version verwendet werden muss. Der Standardwert für alle Elemente ist als false vorbelegt, da diese Eigenschaft standardmäßig nicht existiert. Anschließend muss festgelegt werden, wie der Benutzer diese Funktion benutzen kann. Die linke Maustaste ist bereits mit dem (De)Selektieren eines Elementes (1 × Klick), 66 Bild 4.5: Mesh mit ausgeblendeten Dreiecken (De)Selektieren von mehreren Elementen (STRG + 1 × Klick) und dem Drehen des Objekts (1 × Klick und halten) belegt. Mit der rechten Maustaste wird ein Kontextmenü geöffnet, um diverse Funktionen auszuführen. Somit bleibt nur noch die mittlere Maustaste. Gerade aber bei Notebooks kommt es oft zu Problemen mit dieser, da sie meist nur durch einen Klick auf die linke und rechte Maustaste gleichzeitig emuliert werden muss. Deshalb wird ein einzelner Klick mit der linken Maustaste bei gehaltener Shift-Taste als Tastenkombination verwendet. Zuerst wird die Funktion, die bei einem Klick mit der linken Maustaste aufgerufen wird, erweitert. Wenn der Klick im WebGL-Fenster (renderer.domElement), die Maustaste die linke Maustaste ist, die Maus nicht in Bewegung ist und die Shift- aber nicht die STRG-Taste gedrückt, wird kann damit begonnen werden, das Element zum Ausblenden zu ermitteln. Dafür werden als erstes die angeklickten Elemente abgefragt und anschließend alle bereits ausgeblendeten Elemente aus der Liste entfernt. 1 2 3 i f ( e v e n t . t a r g e t == r e n d e r e r . domElement && e v e n t . b u t t o n == 0 && ! mousewasmoved && e v e n t . s h i f t K e y && ! e v e n t . c t r l K e y ) { v a r i n t e r s e c t s = g e t C l i c k e d O b j e c t s ( mouseX , mouseY ) ; 67 while ( i n t e r s e c t s [ 0 ] . f ace . i n v i s i b l e ) { 4 i n t e r s e c t s . remove ( 0 ) ; 5 6 } 7 i f ( i n t e r s e c t s . length > 0) { hideItem ( i n t e r s e c t s [ 0 ] . face ) ; 8 } 9 10 } Es werden alle Elemente ignoriert, die bereits ausgeblendet sind. Das nächste, was gefunden wird und nicht ausgeblendet ist, wird dann ausgeblendet. Wenn keine Elemente mehr gefunden werden, wird nichts mehr unternommen. Die hideItem Funktion nimmt als Parameter ein Dreieck (face) an und blendet dann wie folgt die Punkte aus: 1 function hideItem ( face ) { i f (! face . i n v i s i b l e ) { 2 3 a t t r i b u t e s . customOpacity . value [ f a c e . a ] = 0 . 0 ; 4 a t t r i b u t e s . customOpacity . value [ f a c e . b ] = 0 . 0 ; 5 a t t r i b u t e s . customOpacity . value [ f a c e . c ] = 0 . 0 ; 6 face . i n v i s i b l e = true ; 7 a t t r i b u t e s . customOpacity . needsUpdate = t r u e ; } 8 9 } Mit diesen Modifikationen ist es nun möglich, das Innere eines Objekts bzw. den Farbverlauf im Inneren anzuschauen. Um die Elemente wieder einzublenden, setzt die Funktion showItem die Werte zurück auf 1,0 und invisible auf false. 1 f u n c t i o n showItem ( f a c e ) { 2 i f ( face . i n v i s i b l e ) { 3 a t t r i b u t e s . customOpacity . value [ f a c e . a ] = 1 . 0 ; 4 a t t r i b u t e s . customOpacity . value [ f a c e . b ] = 1 . 0 ; 5 a t t r i b u t e s . customOpacity . value [ f a c e . c ] = 1 . 0 ; 6 face . i n v i s i b l e = f a l s e ; 7 a t t r i b u t e s . customOpacity . needsUpdate = t r u e ; 8 9 } 68 } 4.2.10 Legende Obwohl die 3D-Modelle nach der Berechnung bereits bunt sein können, benötigt man doch eine ordentliche Legende, um diese Farben sinnvoll interpretieren und nutzen zu können. Es gibt zwei Möglichkeiten, eine solche Legende mit WebGL zu generieren. Die erste Möglichkeit ist es, die Art der Umsetzung einer Legende von OpenGL zu übernehmen. Die Legende wird hierbei komplett selbst in OpenGL bzw. hier WebGL gezeichnet. Listing 4.20 zeigt einen Ausschnitt aus einem früheren Hochschul-Projekt. Dieser Code ist dafür zuständig eine Legende zu zeichnen, die einen Farbverlauf von Blau über Grün nach Rot darstellt. Die daraus folgende Legende wird in Bild 4.6 dargestellt. Es sind im Code Kommentare eingefügt, um die wichtigsten Bereiche abzugrenzen und deren Bedeutung zu verdeutlichen. Die einzelnen mathematischen Funktionen sollen hier nicht weiter erläutert werden, da dieses Beispiel nur dazu dient zu zeigen, welch ein (Code-) Aufwand mit der Zeichnung einer Legende im reinen WebGL bzw. hier OpenGL verbunden ist. Bild 4.6: Legende in OpenGL Listing 4.20: Legende Zeichnen in OpenGL 1 // Legende z e i c h n e n 2 g l B e g i n (GL QUADS ) ; 3 { 4 // F a r b v e r l a u f e r s t e l l e n 5 float lvar ; 6 f l o a t steps = 15.0 f ; 7 f l o a t deltaLvar = 0.9 f / steps ; 8 float faktor ; 9 f l o a t green ; 10 f l o a t blue ; 11 f l o a t red ; 12 f o r ( l v a r = 0 . 0 f ; l v a r < 0 . 9 f ; l v a r += d e l t a L v a r ) 13 { 14 f l o a t c o l o r V a l u e = max x / s t e p s ; 15 float colorValue1 = lvar ∗ colorValue ∗ (1.0 / deltaLvar ); 69 float colorValue2 = ( lvar + deltaLvar ) ∗ colorValue 16 ∗ (1.0/ deltaLvar ); 17 18 19 f a k t o r = c o l o r V a l u e 1 / max x ; 20 g r e e n = (−8 ∗ f a k t o r ∗ f a k t o r ) + ( 8 ∗ f a k t o r ) − 0 . 8 ; 21 g r e e n = fmax ( g r e e n , 0 ) ; 22 23 b l u e = (−6 ∗ f a k t o r ∗ f a k t o r ) + 1 ; 24 b l u e = fmax ( b l u e , 0 ) ; 25 26 r e d = (−6 ∗ f a k t o r ∗ f a k t o r ) + ( 1 6 ∗ f a k t o r ) − 7 ; 27 r e d = fmax ( r ed , 0 ) ; 28 g l C o l o r 3 f ( re d , g r e e n , b l u e ) ; 29 30 31 g l V e r t e x 3 f ( −1.9 f + l v a r , −1.9 f , 0 . 0 f ) ; 32 g l V e r t e x 3 f ( −1.9 f + l v a r , −1.8 f , 0 . 0 f ) ; 33 f a k t o r = c o l o r V a l u e 2 / max x ; 34 35 36 g r e e n = (−8 ∗ f a k t o r ∗ f a k t o r ) + ( 8 ∗ f a k t o r ) − 0 . 8 ; 37 g r e e n = fmax ( g r e e n , 0 ) ; 38 39 b l u e = (−6 ∗ f a k t o r ∗ f a k t o r ) + 1 ; 40 b l u e = fmax ( b l u e , 0 ) ; 41 42 r e d = (−6 ∗ f a k t o r ∗ f a k t o r ) + ( 1 6 ∗ f a k t o r ) − 7 ; 43 r e d = fmax ( r ed , 0 ) ; 44 g l C o l o r 3 f ( re d , g r e e n , b l u e ) ; 45 46 47 g l V e r t e x 3 f ( −1.9 f + l v a r + d e l t a L v a r , −1.8 f , 0 . 0 f ) ; 48 g l V e r t e x 3 f ( −1.9 f + l v a r + d e l t a L v a r , −1.9 f , 0 . 0 f ) ; } 49 50 } 51 52 70 glEnd ( ) ; 53 // B e s c h r i f t u n g L i n k s 54 glColor3f (0.0 , 0.0 , 0.0); 55 s p r i n t f ( text , ”%1 . 3 f ” , m i n x ) ; 56 g l R a s t e r P o s 2 f ( −1.9 f , −1.79 f ) ; 57 g l P r i n t ( text ) ; 58 59 // B e s c h r i f t u n g R e c h t s 60 s p r i n t f ( text , ”%1 . 3 f ” , max x ) ; 61 g l R a s t e r P o s 2 f ( −1.35 f , −1.79 f ) ; 62 g l P r i n t ( text ) ; 63 64 // R o t e s V i e r e c k und X 65 glColor3f (1.0 ,0 ,0.0); 66 g l R a s t e r P o s 2 f ( −0.85 f , −1.79 f ) ; 67 g l P r i n t ( ”X ” ) ; 68 g l B e g i n (GL QUADS ) ; 69 g l V e r t e x 3 f ( −0.9 f , −1.9 f , 0 . 0 f ) ; 70 g l V e r t e x 3 f ( −0.9 f , −1.8 f , 0 . 0 f ) ; 71 g l V e r t e x 3 f ( −0.7 f , −1.8 f , 0 . 0 f ) ; 72 g l V e r t e x 3 f ( −0.7 f , −1.9 f , 0 . 0 f ) ; 73 glEnd ( ) ; 74 75 // Gr ü n e s V i e r e c k und Y 76 glColor3f (0.0 ,1 ,0.0); 77 g l R a s t e r P o s 2 f ( −0.55 f , −1.79 f ) ; 78 g l P r i n t ( ”Y ” ) ; 79 g l B e g i n (GL QUADS ) ; 80 g l V e r t e x 3 f ( −0.6 f , −1.9 f , 0 . 0 f ) ; 81 g l V e r t e x 3 f ( −0.6 f , −1.8 f , 0 . 0 f ) ; 82 g l V e r t e x 3 f ( −0.4 f , −1.8 f , 0 . 0 f ) ; 83 g l V e r t e x 3 f ( −0.4 f , −1.9 f , 0 . 0 f ) ; 84 glEnd ( ) ; 85 86 // B l a u e s V i e r e c k und Z 87 glColor3f (0.0 ,0 ,1.0); 88 g l R a s t e r P o s 2 f ( −0.25 f , −1.79 f ) ; 89 g l P r i n t (”Z ” ) ; 71 90 g l B e g i n (GL QUADS ) ; 91 g l V e r t e x 3 f ( −0.3 f , −1.9 f , 0 . 0 f ) ; 92 g l V e r t e x 3 f ( −0.3 f , −1.8 f , 0 . 0 f ) ; 93 g l V e r t e x 3 f ( −0.1 f , −1.8 f , 0 . 0 f ) ; 94 g l V e r t e x 3 f ( −0.1 f , −1.9 f , 0 . 0 f ) ; 95 glEnd ( ) ; 96 97 // Text : ” M e s s r e i h e : 5” 98 glColor3f (0.0 , 0.0 , 0.0); 99 s p r i n t f ( text , ” M e s s r e i h e : %d ” , c u r r e n t M e a s u r e d S e r i e s ) ; 100 g l R a s t e r P o s 2 f ( 0 . 0 f , −1.9 f ) ; 101 g l P r i n t ( text ) ; Da diese Umsetzung als sehr lang und unübersichtlich empfunden wird, muss eine Alternative entwickelt werden. Hier wird die Legende komplett mit HTML und JavaScript generiert und einfach über das WebGL-Fenster gelegt. Das HTML-Element für die Legende besteht aus einem div-Element als Container für den Inhalt, einem img-Element für das Bild des Farbverlaufs und diversen div-Elementen für die Texte (Listing 4.21). Listing 4.21: HTML Aufbau der Legende 1 <d i v i d =”legend ” c l a s s =”legend”> 2 3 4 5 6 7 8 <d i v c l a s s =” n u m b e r b a s i c ” s t y l e =” l e f t : 10 px ; ” i d =” v a l u e m i n ”>15,3</ d i v > <d i v c l a s s =” n u m b e r b a s i c ” s t y l e =” l e f t : 140 px ; text −a l i g n : c e n t e r ; ” i d =” v a l u e m i d ”>25,7</ d i v > <d i v c l a s s =” n u m b e r b a s i c ” s t y l e =” l e f t : 270 px ; text −a l i g n : r i g h t ; ” i d =”v a l u e m a x ”>38,9</ d i v > <img s r c =”d a t a / img / l i n h o t . png ” c l a s s =” i m g b a s i c ” i d =” i m g l e g e n d ”/> 9 </d i v > Zu den HTML-Elementen kommen noch einige CSS-Style-Informationen, womit der Aufbau der Legende abgeschlossen ist (Listing 4.22). Cascading Style Sheets; spezielle Sprache ” für die Formatierung von Inhalten auf Webseiten“ (vgl. [KERS2005, S. 993]). Listing 4.22: CSS-Style-Informationen der Legende 1 . legend { 2 z−i n d e x : 5 0 0 0 ; 3 position : absolute ; 72 4 l e f t : 150 px ; 5 t o p : 150 px ; 6 w i d t h : 400 px ; 7 h e i g h t : 65 px ; 8 background −c o l o r : #d3d3d3 ; 9 } 10 11 . numberbasic { 12 position : absolute ; 13 t o p : 5 px ; 14 w i d t h : 120 px ; 15 } 16 17 . imgbasic { 18 p a d d i n g : 10 px ; 19 t o p : 25 px ; 20 position : absolute ; 21 w i d t h : 380 px ; 22 h e i g h t : 25 px ; 23 } Um zum Abschluss die Legende aktualisieren zu können, muss dafür noch eine Funktion geschrieben werden. Diese ist in Listing 4.23 abgebildet. Listing 4.23: Legende aktualisieren 1 f u n c t i o n u p d a t e L e g e n d V a l u e s ( type , min , mid , max) { 2 document . g e t E l e m e n t B y I d ( ” i m g l e g e n d ” ) . s r c = 3 ” d a t a / img / l i n ”+type +”. png ” ; 4 min = min . t o S t r i n g ( ) . s u b s t r ( 0 , 1 5 ) 5 document . g e t E l e m e n t B y I d ( ” v a l u e m i n ” ) . i n n e r T e x t = min ; 6 mid = mid . t o S t r i n g ( ) . s u b s t r ( 0 , 1 5 ) 7 document . g e t E l e m e n t B y I d ( ” v a l u e m i d ” ) . i n n e r T e x t = mid ; 8 max = max . t o S t r i n g ( ) . s u b s t r ( 0 , 1 5 ) 9 document . g e t E l e m e n t B y I d ( ” v a l u e m a x ” ) . i n n e r T e x t = max ; 10 } Bild 4.7 auf Seite 74, zeigt die finale Legende in verschiedenen Variationen. 73 (a) Typ: hsv (b) Typ: ihsv (c) Typ: hot (d) Typ: jet Bild 4.7: Verschiedene Legendenarten in HTML und JavaScript 4.3 Umsetzung der sonstigen GUI-Elemente Die Möglichkeiten zur Umsetzung der sonstigen GUI-Elementen, neben dem WebGLFenster, werden in diesem Kapitel vorgestellt. Für die Umsetzung der GUI-Elemente gibt es verschiedene Herangehensweisen. Zunächst könnten alle Komponenten mit reinem HTML umgesetzt werden. Zusätzliche Komponenten, wie beispielsweise ein Grid, eine Toolbar oder ein Popup-Fenster (nicht die, die ein neues Browser-Fenster oder -Tab öffnen), müssten dann selbst erstellt werden. Basierend auf den Anforderungen aus Kapitel 3.1 auf Seite 24 werden definitiv schon einmal die zusätzlichen nicht-HTML-Komponenten benötigt: • Ein Form“-Element, in dem der Benutzer Texte und/oder Zahlen z.B. für Parameter ” eintragen kann • Ein Grid“-Element für die Logmeldungen der Fehlerkonsole ” • Ein Layout“-Element, um die Seite in funktionelle Bereiche aufzuteilen (Projekt” baum, WebGL, Fehlerkonsole, Menü) • Ein Menü“-Element für die Menüelemente ” • Ein Window“-Element für die Eingabe der Parameter oder Informationen zu Ele” menten Diese fünf Komponenten können selbst erstellt werden. Bereits für ein einfaches Win” dow“ -Element werden jedoch schon über 150 Zeilen JavaScript-, CSS- und HTML-Code 74 benötigt, wie das Beispiel (siehe: [AROR2014]) zeigt. Dieser Ansatz ist mit sehr viel Zeit und Aufwand verbunden. Es müssen nicht nur die verschiedenen Komponenten generiert, sondern auch die Kompatibilität für die verschiedenen Browser (Chrome, Firefox, Safari, Opera und Internet Explorer) sichergestellt werden. Die zweite Herangehensweise ist, eine JavaScript-Bibliothek zu verwenden, die bereits alle benötigten Komponenten beinhaltet. Für die Umsetzung von GUI-Elementen im Web gibt es eine große Auswahl an JavaScript-Bibliotheken, aus der man wählen kann. In die nähere Auswahl für dieses Projekt sind die vier folgenden, wohl auch bekanntesten, JavaScriptBibliotheken gekommen: • AngularJs – http://www.angularjs.org/ (Abgerufen: 17.02.14) • jQuery UI – http://jqueryui.com/widget/ (Abgerufen: 17.02.14) • Kendo UI – http://www.telerik.com/kendo-ui (Abgerufen: 17.02.14) • dhtmlxSuite – http://dhtmlx.com/ (Abgerufen: 17.02.14) Weiter oben in diesem Kapitel wurden bereits die Elemente definiert, die jede Bibliothek unterstützen muss: Form, Grid, Layout, Menü und Window. Die Tabelle 4.3 zeigt die verschiedenen JavaScript Bibliotheken und die Elemente, die unterstützt werden. Tabelle 4.3: Elemente der Javascript UI Bibliotheken Name Form Grid Layout Menü Window AngularJs + + + jQuery UI + + + Kendo UI + + + + + dhtmlxSuite + + + + + Basierend auf den unterstützten Funktionen sind AngularJs und jQuery UI nicht einsetzbar für dieses Projekt. Sowohl Kendo UI als auch dhtmlxSuite erfüllen alle Andorderungen. Kendo UI wird leider nur als kommerzielle Lösung angeboten und der Preis für die Komponenten beginnt bei $699,00 (siehe: [TELE2014]). Die dhtmlxSuite hingegen gibt es sowohl als Standard als auch als Professional Edition (siehe: [DINA2014]). Die Standard-Edition ist lizenziert unter der GNU-GPL und kann somit ohne Probleme im Projekt verwendet werden. Die Professional-Edition erweitert die Standard-Edition um die Komponente dhtmlxTreeGrid und einige weitere Funktionen, die hier jedoch nicht benötigt werden. Da sich die dhtmlxSuite über das Ausschlusskriterium als einzige Möglichkeit bewährt 75 hat, wird diese hier verwendet. Ein zusätzlicher Vorteil von dhtmlxSuite gegenüber Kendo UI liegt darin, dass bereits Know-How im Bereich dhtmlxSuite vorliegt und es daher ein unnötiger Mehraufwand wäre, sich noch zusätzlich in Kendo UI einzuarbeiten. 4.4 (Mögliche) Techniken zur Verbesserung der Datenübertragung In diesem Kapitel werden Möglichkeiten zur Verbesserung der Datenübertragung zwischen Client und Server dargestellt. Diese Überlegungen sind rein theoretisch und wurden aus Zeitgründen nicht umgesetzt. 4.4.1 Coarsening Zum einen könnte das Gitter mit Hilfe von Coarsening und Refinement optimiert werden. Coarsening ist ein Prozess, bei dem Elemente aus dem Gitter entfernt werden und beim Refinement werden Elemente dem Gitter hinzugefügt. Für die Optimierung eines Gitters werden beide Prozesse verwendet. Mit Coarsening werden an unwichtigen Stellen im Gitter Elemente herausgenommen und das Gitter somit an dieser Stelle gröber gemacht. An den Stellen, die zu wenige Elemente haben, werden diese mit Hilfe von Refinement hinzugefügt. Eine genaue Erklärung der Methode kann in der Quelle gefunden werden (vgl. [ShDeWoBe+2010, S. 1ff]). 4.4.2 Trennung von Gitter- und Lösungsdaten Eine weitere Möglichkeit wäre, die Struktur des Gitters und die Werte der Lösung getrennt voneinander zu übertragen. Sobald der Benutzer eine Gitter-Datei auf den Server läd, wird diese für ihn eingelesen, für die weitere Verarbeitung konvertiert und anschließend an den Client zurückgesendet. Zuerst wird dabei die Struktur des Gitters zusammen mit Startwerten für Farbe, Transparenz und Wert der Lösung übertragen. Hier könnte die erste Optimierung stattfinden. Anstelle Farbe (Grau), Transparenz (1.0) und Lösung (0) beim ersten Mal mit zu übertragen, können diese Werte automatisch im Client gesetzt werden, wenn keine Lösung vorhanden ist. JavaScript nutzt die UTF-16-Kodierung des Unicode” Zeichensatzes“ (vgl. [FLAN2012, S. 36f]). Jedes Zeichen in einer JavaScript-Zeichenkette hat somit 16 Bit, was 2 Byte entspricht (1 Byte = 8 Bit). Die Byte-Größe einer Zeichenkette lässt sich mit der folgenden Funktion bestimmen. 1 2 76 function getByteLength ( z e i c h e n k e t t e ){ return z e i c h e n k e t t e . length ∗ 2 ; 3 } Diese Funktion wird zur Bestimmung der Länge für die folgenden Zeichenketten verwendet. Pro Punkt würden damit 36 Byte für die Farbe (3 · 10 Byte für die Farbwerte (0,647) und 3 · 2 Byte für das Trennzeichen), 8 Byte für die Transparenz (3 · 2 Byte für den Wert (1.0) und 2 Byte für das Trennzeichen) und 4 Byte für den Wert der Lösung (2 Byte für den Wert (0) und 2 Byte für das Trennzeichen) wegfallen. Das führt zusammen zu einer Ersparnis von 48 Byte pro Punkt und 144 Byte pro Dreieck. Bei einer Datei mit 100.000 Dreiecken entspricht das 14.400.000 Byte (13,73 MB). 77 5 Fazit und Ausblick Ziel dieser Arbeit war es, anhand des Problemkomplexes aus dem Projekt SimCloud (siehe Kapitel 1 auf Seite 4) prototypisch darzustellen, wie performant eine Visualisierung von 3D-Daten im Browser gelingen könnte. Zu Beginn wurden die verschiedenen Technologien zur Übertragung von Daten zwischen Browser und Client miteinander verglichen. Dabei stellte sich WebSocket als die beste Möglichkeit der verglichenen heraus und kam zur Verwendung. Anschließend wurden die verschiedenen Technologien zur Visualisierung von technischen 3D-Daten im Browser vorgestellt. Von diesen hat sich WebGL als am vorteilhaftesten erwiesen. Mithilfe dieser beiden Technologien war es möglich, die abgeleiteten Funktionen aus der wxWidgetGUI komplett in einem Webbrowser abzubilden. Zum Ende der Bachelor-Arbeit beinhaltete die WebGUI alle Funktionen, die in der wxWidgetGUI zu Beginn implementiert waren. Weitergehend war es möglich, neue Funktionen, die während der Bachelor-Arbeit hinzugefügt wurden, zu einem großen Teil mit zu übernehmen. Durch die Verwendung eines selbst definierten Übertragungsformats (RAW-Format) anstelle von JSON konnte die Größe der zu übertragenden Gitter-Daten (Server zu Client) auf 45% der Originalgröße reduziert werden. Weiterhin wurde es mithilfe eines eigens entwickelten Vertex- und Fragement-Shaders und dem THREE.ShaderMaterial möglich, Transparenz für einzelne Punkte zu ermöglichen und somit die Anforderungen an das Ausblenden von Elementen zu erfüllen. In dem Zeitraum dieser Bachelor-Arbeit wurde somit ein funktionsfähiger Prototyp generiert, der die wichtigsten Funktionen bereits enthält. Nach dem Abschluss dieser Arbeit wird die Entwicklung weitergeführt und die verbleibenden Funktionen in der WebGUI implementiert. Diese betreffen vor allem das Starten und Steuern der Simulation. Die dafür benötigten Funktionen im Backend sind erst kurz 78 vor Abschluss dieser Arbeit fertiggestellt worden und konnten daher noch nicht mit aufgenommen werden. Des weiteren muss das FEMInterface für die Benutzung von mehr als einem Benutzer angepasst werden. Es können sich zwar mehrere Personen mit dem Server verbinden, Änderungen vornehmen und Gitter-Dateien hochladen, sämtliche Änderungen gelten aber stets für alle Benutzer. Die in Kapitel 4.4 auf Seite 76 angesprochenen Techniken zur Verbesserung der Datenübertragung werden ebenso umgesetzt. Dies wird zu einer Verringerung des Datenverkehrs und damit einer schnelleren Applikation führen. Diese letzten Änderungen werden innerhalb der nächsten Monate durchgeführt und werden bei erfolgreicher Umsetzung eventuell bald zu einer Ablösung der wxWidgetGUI durch die WebGUI führen. 79 Abbildungsverzeichnis 80 2.1 2.2 2.3 2.4 2.5 2.6 SVG Grafik aus Listing 2.1 . . . . . . . . . . . . SVG Grafik aus Listing 2.1 nach Modifizierung . Beispiele für Grafikanwendungen im Browser . . Polling . . . . . . . . . . . . . . . . . . . . . . . Long-Polling . . . . . . . . . . . . . . . . . . . . WebSocket Kommunikation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 9 12 18 18 20 3.1 3.2 3.3 3.4 3.5 3.6 wxWidgetGUI aus dem Projekt SimCloud Einzelnes Element ausgewählt . . . . . . . Problem und Randwerte definieren . . . . Entwurf des Hardware-Dialogs . . . . . . . Softwarearchitektur . . . . . . . . . . . . . Softwarearchitektur (Neu) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 26 27 27 32 33 4.1 4.2 4.3 4.4 4.5 4.6 4.7 Aufbau RAW Objekt . . . . . . . . . . . . . . . . . . Perspektivische und Orthographische Ansichten . . . Quadrat WebGL / Three.js . . . . . . . . . . . . . . Mesh mit ausgewählten Dreiecken . . . . . . . . . . . Mesh mit ausgeblendeten Dreiecken . . . . . . . . . . Legende in OpenGL . . . . . . . . . . . . . . . . . . Verschiedene Legendenarten in HTML und JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 45 50 61 67 69 74 . . . . . . . . . . . . . . . . . . Literaturverzeichnis [AROR2014] Arora, Varun How to create Popup Windows with JavaScript (2014) http://mrbool.com/how-to-create-popup-windows-withjavascript/27007 (21.03.2014) [BETC2014] BETC DIGITAL Chrome Experiment “Graffiti General“ (2014) http://www.graffitigeneral.com/en (10.02.2014) [BIBL2013a] Bibliographisches Institut GmbH Echtzeit http://www.duden.de/rechtschreibung/Echtzeit (21.03.2014) [BIBL2013b] Bibliographisches Institut GmbH Echtzeitbetrieb http://www.duden.de/rechtschreibung/Echtzeitbetrieb (21.03.2014) [BOOS2014] Boost Community Boost C++ Libraries (2014) http://www.boost.org/ (21.03.2014) [BRAN2014] Brandel, Jono Chrome Experiment “Two.js Particle Sandbox“ (2014) http://jonobr1.github.io/two.js/examples/particlesandbox.html (10.02.2014) [BRUE2003] Brünner, Arndt Der Kodierungs-Standard base64 (2003) http://www.arndt-bruenner.de/mathe/scripts/base64.htm (24.03.14) 81 [CABE2014a] 82 Ricardo Cabello three.js / docs (2014) http://threejs.org/docs/ (21.03.2014) Kapitel: Scene: http://threejs.org/docs/#Reference/Scenes/Scene (21.03.2014) OrthographicCamera: http://threejs.org/docs/#Reference/ Cameras/OrthographicCamera (21.03.2014) PerspectiveCamera: http://threejs.org/docs/#Reference/ Cameras/PerspectiveCamera (21.03.2014) Geometry: http://threejs.org/docs/#Reference/Core/Geometry (21.03.2014) Material: http://threejs.org/docs/#Reference/Materials/ Material (21.03.2014) MeshBasicMaterial: http://threejs.org/docs/#Reference/ Materials/MeshBasicMaterial (21.03.2014) Mesh: http://threejs.org/docs/#Reference/Objects/Mesh (21.03.2014) Light: http://threejs.org/docs/#Reference/Lights/Light (21.03.2014) PointLight: http://threejs.org/docs/#Reference/Lights/ PointLight (21.03.2014) WebGLRenderer: http://threejs.org/docs/#Reference/ Renderers/WebGLRenderer (21.03.2014) Color: http://threejs.org/docs/#Reference/Math/Color (21.03.2014) Raycaster: http://threejs.org/docs/#Reference/Core/ Raycaster (21.03.2014) Projector: http://threejs.org/docs/#Reference/Core/Projector (21.03.2014) ShaderMaterial: http://threejs.org/docs/#Reference/ Materials/ShaderMaterial (21.03.2014) [CABE2014b] Ricardo Cabello JavaScript 3D library. http://threejs.org/ (2014) https://github.com/mrdoob/three.js/ [DEBE] Debeiss, N. svg3d – adding a third dimension to SVG pictures https://code.google.com/p/svg3d/ (21.03.2014) [DEVE2014a] Deveria, Alexis Can I use SVG? Compatibility table for support of SVG in desktop and mobile browsers. http://caniuse.com/svg (24.03.12) [DEVE2014b] Deveria, Alexis Can I use WebGL? Compatibility table for support of WebGL in desktop and mobile browsers. http://caniuse.com/webgl (24.03.12) [DINA2014] Dinamenta, UAB Editions and Licenses (2014) http://dhtmlx.com/docs/products/licenses.shtml#price_ request (21.03.2014) [EaJo2001] Eastlake, D. und Jones, P. US Secure Hash Algorithm 1 (SHA1) (2001) Network Working Group http://tools.ietf.org/pdf/rfc3174.pdf (24.03.12) [FEME2011] Fette, Ian und Melnikov, Alexey The WebSocket Protocol (2011) http://tools.ietf.org/pdf/rfc6455.pdf (02.02.2014) Internet Engineering Task Force (IETF) ISSN: 2070-1721 83 [FiGeMoFr+1999] Fielding, R. und Mogul, J. C. und Frystyk, H. und Masinter, L. und Leach, P. und Berners-Lee, T. Hypertext Transfer Protocol – HTTP/1.1 (1999) http://www.w3.org/Protocols/HTTP/1.1/rfc2616.pdf (18.02.2014) [FLAN2012] Flanagan, David Javascript ein umfassendes Referenzwerk 2012, Köln, O’Reilly Verlag ISBN: 978-3-86899-135-2 [FROC2005] Frochte, Jörg Ein Splitting-Algorithmus höherer Ordnung für die Navier-StokesGleichung auf der Basis der Finite-Element-Methode (2005) Universität Duisburg Essen, Dissertation http://duepublico.uni-duisburg-essen.de/servlets/ DerivateServlet/Derivate-13644/JFrochte_Diss.PDF (20.03.2014) [GHOS2010] Ghosheh, Hisham XNA Picking Tutorial Part I (2010) http://ghoshehsoft.wordpress.com/2010/11/25/xna-pickingtutorial-part-i/ (24.03.14) [GILL2013] Gill, Kevin M. Chrome Experiment “Satellite Viewer“ (2013) http://satellites.wthr.us (10.02.2014) [GOOG2014] Google v8 – V8 JavaScript Engine (2014) https://code.google.com/p/v8/ (21.03.2014) [GREG2012] Greggman WebGL – 2D vs 3D libraries (2012) http://greggman.github.io/webgl-fundamentals/webgl/ lessons/webgl-2d-vs-3d-library.html (08.03.2014) 84 [HoDi2000] John A. Hoxmeier, Chris DiCesare System Response Time and User Satisfaction: An Experimental Study of Browser-based Applications (2000) AMCIS 2000 Proceedings. Paper 347. http://aisel.aisnet.org/amcis2000/347 (15.03.14) http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1. 99.2770&rep=rep1&type=pdf (15.03.14) [JAME2012] Marshall, James HTTP Made Really Easy (2012) http://www.jmarshall.com/easy/http/ (18.02.2014) http://www.jmarshall.com/easy/http/http_footnotes.html# getsubmit (18.02.2014) [JONE2014] Jones, Brandon glMatrix – Javascript Matrix and Vector library for High Performance WebGL apps (2014) http://glmatrix.net/ (21.03.2014) [KERS2005] Kersken, Sascha Handbuch für Fachinformatiker : der Ausbildungsbegleiter 2005, Bonn, Galileo Press ISBN: 3-89842-668-8 [KHRO2012] Khronos Group WebGL and OpenGL Differences (2012) http://www.khronos.org/webgl/wiki_1_15/index.php/WebGL_ and_OpenGL_Differences (10.02.2014) [KHRO2013a] Khronos Group User Contributions (2013) http://www.khronos.org/webgl/wiki/User_Contributions (21.03.2014) [KHRO2013b] Khronos Group Depth Test (2013) http://www.opengl.org/wiki/Depth_Test (21.03.2014) 85 [KHRO2014a] Khronos Group The Khronos Group – Connecting Software to Silicon (2014) http://www.khronos.org (17.02.2014) [KHRO2014b] Khronos Group OpenGL Shading Language https://www.opengl.org/documentation/glsl/ (21.03.2014) [NH3D2011] Nh3d (http://wiki.blender.org/index.php/User:Nh3d) File:Manual-Part-I-3DPerspective.png (2011) http://wiki.blender.org/index.php/File:Manual-Part-I3DPerspective.png [PARI2012] Parisi, Tony WebGL Up and Running 2012, Sebastopol, CA, O’Reilly Verlag ISBN: 978-1-449-32357-8 [PaWiSwSt+2008] Patonnier, J. und Williams, S. und Swisher, J. und Strehl, M. und Hobson, T. und regjo“ und alenia“ und Shirak“ und yyss“ und ” ” ” ” Wjjohnst“ und andrewbunner“ und Thrill“ ” ” ” Paths (2008) 2005-2014 Mozilla Developer Network and individual contributors https://developer.mozilla.org/en-US/docs/Web/SVG/ Tutorial/Paths (11.03.2014) [RONA2012] Ronacher, Armin Websockets 101 (2012) http://lucumr.pocoo.org/2012/9/24/websockets-101/ (24.03.14) [RUQU2012] Rupp, Chris und Queins Stefan UML 2 glasklar: Praxiswissen für die UML-Modellierung 2012, München, Carl Hanser Verlag ISBN: 978-3-446-43057-0 86 [SCHA2009] Schallehn, Eike Grundlagen der Computergrafik http://wwwiti.cs.uni-magdeburg.de/iti_db/lehre/gif/gif09_ 10.pdf (21.03.2014) [SCHO2005] Scholz, Peter Softwareentwicklung eingebetteter Systeme – Grundlagen, Modellierung, Qualitätssicherung (2005) 2005, Springer Verlag http://link.springer.com/book/10.1007/3540-27522-3 (24.03.2014) [SCHO2014] Schoeberl, Joachim Netgen Mesh Generator – NETGEN is an automatic 3d tetrahedral mesh generator (2014) http://sourceforge.net/p/netgen-mesher/wiki/Home/ (21.03.2014) [ShDeWoBe+2010] Shepherd, Jason F. und Dewey, Mark W. und Woodbury, Adam C. und Benzley, Steven E. und Staten, Matthew L. und Owen, Steven J. Adaptive Mesh Coarsening for Quadrilateral and Hexahedral Meshes http://dx.doi.org/10.1016/j.finel.2009.06.024 (25.02.2014) Finite Elements in Analysis and Design (January, 2010) ISSN: 0168-874X [SINI2011] sinisterchipmunk How WebGL works? (2011) http://stackoverflow.com/a/7374194/1106371 (20.02.2014) [TCOM2014] Telekom Deutschland GmbH Alle VDSL- und DSL- Varianten im Vergleich (2014) http://www.telekom.de/is-bin/INTERSHOP.enfinity/WFS/ EKI-PK-Site/de_DE/-/EUR/ViewCategoryTheme-Start? CatalogCategoryID=7W0FC7IVJjYAAAEknrdE.Lku (25.02.2014) [TELE2014] Telerik Buy Kendo UI Complete (2014) http://www.telerik.com/purchase/kendo-ui-complete (21.03.2014) 87 [WEßE2011] Weßendorf, Matthias WebSocket: Annäherung an Echtzeit im Web (2011) http://www.heise.de/-1260189 (18.02.2014) [ZIMM2012] Zimmermann, Albert Basismodelle der Geoinformatik – Strukturen, Algorithmen und Programmierbeispiele in Java 2012, München, Carl Hanser Verlag ISBN: 978-3-446-42953-6 88 Eidesstattliche Erklärung Ich versichere, dass ich die Arbeit selbständig verfasst und keine als die angegebenen Quellen und Hilfsmittel benutzt sowie Zitate kenntlich gemacht habe. Die Regelungen der geltenden Prüfungsordnung zu Versäumnis, Rücktritt, Täuschung und Ordnungsverstoß habe ich zur Kenntnis genommen. Diese Arbeit hat in gleicher oder ähnlicher Form keiner Prüfungsbehörde vorgelegen. Heiligenhaus, den Unterschrift 89