Entdecken Sie eBooks
Kategorien
Entdecken Sie Hörbücher
Kategorien
Entdecken Sie Zeitschriften
Kategorien
Entdecken Sie Dokumente
Kategorien
Betreuer:
Dr. Björn Zehner (UFZ)
Prof. Dr. Ing. habil. Dieter Vyhnal (HTWK Leipzig)
Inhaltsverzeichnis
Abbildungsverzeichnis ................................................................................................................................... 3
1. Einführung ............................................................................................................................................. 4
2. OpenSG .................................................................................................................................................. 5
2.1. Überblick........................................................................................................................................ 5
2.2. Features.......................................................................................................................................... 6
2.2.1. Performance .......................................................................................................................... 6
2.2.2. Multi-Threading .................................................................................................................... 6
2.2.3. Clustering .............................................................................................................................. 7
2.2.4. Erweiterbarkeit ..................................................................................................................... 8
2.2.5. Multi-Plattform ..................................................................................................................... 8
2.3. Ausblick ..........................................................................................................................................8
3. VRPN – Virtual Reality Peripheral Network........................................................................................ 9
3.1. Eingabegeräte-Abstraktion ........................................................................................................... 9
3.2. VRPN-Verbindungsaufbau ......................................................................................................... 10
3.3. Vorteile des Client/Server-Aufbaus ............................................................................................ 11
3.4. Logging.......................................................................................................................................... 11
4. VRPN benutzen ..................................................................................................................................... 11
4.1. Der allgemeine VRPN-Server ...................................................................................................... 11
4.2. Einen eigenen Server schreiben ................................................................................................. 12
4.3. Eine einfache Client-Anwendung ............................................................................................... 13
4.4. SpaceNavigator-Klasse................................................................................................................ 14
4.4.1. Implementierung als VRPN-Client .................................................................................... 16
4.4.2. Implementierung mithilfe des Singleton-Entwurfsmusters ............................................ 16
4.4.3. Initialisierung und Aufbauen einer Verbindung zum VRPN-Server ............................... 16
4.4.4. Bereitstellen des aktuellen Zustands des SpaceNavigators .............................................. 17
4.4.5. Verschiedene Funktionsmodi............................................................................................. 18
4.4.6. Achsenbeschränkung .......................................................................................................... 18
4.4.7. Invertierung der Achsen ..................................................................................................... 19
4.4.8. Standard-Buttonbelegung .................................................................................................. 19
4.4.9. Die Klasse benutzen ............................................................................................................ 19
4.4.10. Beispielanwendung: Bewegen und Drehen eines Würfels in OpenSG ............................20
4.5. Der SpaceNavigatorSSM ............................................................................................................. 24
4.5.1. Ableiten von SimpleSceneManager ................................................................................... 24
4.5.2. Auswählen der Hoch-Achse ............................................................................................... 26
4.5.3. Kamerasteuerung mit fester Höhe über Grund ................................................................28
2
4.5.4. Auswählen von Objekten per Mausklick ...........................................................................30
4.5.5. Ausgewählte Objekte rotieren und verschieben ................................................................ 32
4.5.6. Auf der Oberfläche bewegen............................................................................................... 34
4.5.7. Optimierte Bewegung auf der Oberfläche ......................................................................... 34
4.5.8. Weitere Funktionen ............................................................................................................ 35
4.5.9. Beispielanwendung: Bewegung auf einem Stadtmodell ................................................... 35
4.5.10. Beispielanwendung: Bewegung auf einem Landschaftsmodell ....................................... 36
4.6. Installation und Testen im VR-Pool der HTWK Leipzig........................................................... 37
4.6.1. Den SpaceNavigator-Server einrichten ............................................................................. 37
4.6.2. Die Testanwendungen (SpaceNavigator-Client) einrichten .............................................38
4.7. VRPN erweitern ...........................................................................................................................38
5. CD-Inhalt .............................................................................................................................................38
5.1. Ordner Client ...............................................................................................................................38
5.2. Ordner download ........................................................................................................................38
5.3. Ordner Server ..............................................................................................................................38
5.4. Ordner SpaceNavigator ............................................................................................................. 39
5.5. Ordner opensg ............................................................................................................................. 39
5.6. Ordner vrpn ................................................................................................................................. 39
Literaturverzeichnis .....................................................................................................................................40
Abbildungsverzeichnis
Abbildung 1 - Eingabegeräte von 3DConnexion, links Space Navigator, rechts Space Pilot ..................... 4
Abbildung 2 - Schematischer Aufbau des Visualisierungszentrums im UFZ ............................................. 5
Abbildung 3 - Anwendungen nutzen ein Eingabegerät über verschiedene Klassen ................................ 10
Abbildung 4 - Die Verarbeitung der Space Navigator-Eingaben .............................................................. 17
Abbildung 5 - Der Aufbau der Szene ........................................................................................................... 21
Abbildung 6 - Die Beispielanwendung zum Drehen eines Würfels .......................................................... 24
Abbildung 7 – links: OpenSG-Koordinatensystem (Y-Achse ist Hochachse), rechts: rechtshändiges
Koordinatensystem mit Z-Achse als Hochachse ........................................................................................ 27
Abbildung 8 - Ein Objekt wird mithilfe der Maus ausgewählt und mit dem SpaceNavigator gedreht und
angehoben .................................................................................................................................................... 36
3
1. Einführung
Im Rahmen des als Pflichtmodul im 3. Fachsemester im Masterstudiengang Medien-
informatik durchzuführenden Master-Projektes stellt die vorliegende Arbeit eine praktische
Einführung in die Programmierung mit der netzwerkbasierten Eingabegeräte-Bibliothek
VRPN1 dar. Es wird gezeigt, wie 6dof2-Eingabegeräte von 3DConnexion (siehe Abbildung 2)
in VRPN eingebunden werden und Programmen über das Netzwerk Zugriff darauf verschafft.
Abbildung 1 - Eingabegeräte von 3DConnexion, links Space Navigator, rechts Space Pilot
Dies ist eine typische Anforderung im Bereich der Virtual Reality und der Visualisierung. VR-
Systeme bestehen meistens aus mehreren Rechnern, die über ein Netzwerk zu einem
Rechencluster verbunden werden. Der Rechencluster ist meistens räumlich vom
Displaysystem getrennt. Es wäre sehr unpraktisch Eingabegeräte immer an den Rechner
anschließen zu müssen, auf dem die Anwendung gerade läuft. Mit einer netzwerkgestützten
Eingabemethode können die Eingabegeräte an einen Rechner angeschlossen werden, der z.B.
in der Nähe des Displaysystems steht. Abbildung 2 verdeutlicht einen beispielhaften Aufbau
des Visualisierungszentrums im UFZ.
Im Vorführ-Raum
Raum befindet sich der Rechner an dem das Eingabegerät angeschlossen
angeschlosse ist. Der
eigentliche Anwendungsrechner kann aber räumlich vom Vorführ-Raum
Vorführ Raum getrennt über das
Netzwerk die Eingaben des Benutzers bekommen. Das Rendering erfolgt wiederum ebenfalls
räumlich getrennt auf dem Render-Cluster.
Render Das Render-Cluster
Cluster steht über das
da Netzwerk in
Verbindung mit dem Anwendungsrechner.
2. OpenSG
2.1. Überblick
OpenSG ist ein portables Szenengraphen4-System
System für die Erstellung von Echtzeit-Grafik-
Echtzeit
Anwendungen, in erster Linie für Virtual Reality.. OpenSG wird als OpenSource entwickelt
und kann frei verwendet werden. Es läuft auf zahlreichen Plattformen und Betriebssystemen.
Betriebssystemen
OpenSG baut auf dem hardwarenahen OpenGL auf, nutzt es zur Grafikausgabe und bildet
dabei eine höhere Abstraktionsschicht. Dabei
bei verfügt der Szenengraph über die komplette
Struktur der Szene und kann mit dieser Information die Berechnung der Grafik auf einem
hohen Level optimieren.
2.2. Features
2.2.1. Performance
Um die ständig steigende Rechengeschwindigkeit von aktueller Grafikhardware optimal
auszunutzen, ist es sehr wichtig, die Hardware ausreichend schnell mit zu verarbeitenden
Daten zu beliefern. Die Rechengeschwindigkeit entwickelt sich schneller als die
Speicherbandbreiten zwischen dem Hauptspeicher und der Grafikhardware. Aus diesem
Grund muss die Grafikhardware mit großen und homogenen Daten versorgt werden. Jeder
zu transferierende Datenblock verursacht einen gewissen Initialisierungsoverhead.
Außerdem kommt es zu sogenannten State-Änderungen, wie z.B. das Wechseln der
verwendeten Textur oder das Laden eines Shaders5, die ebenfalls an der Performance zehren.
OpenSG minimiert diesen Setup-Overhead, indem zu zeichnende Objekte, sortiert nach
State-Änderungen6 und Render-Eigenschaften, gezeichnet werden.
Der Szenengraph hat einen globalen Überblick über seinen Inhalt. Im Gegensatz dazu
verarbeitet der Grafiktreiber immer nur eine sehr kleine Menge an Daten. Der Szenengraph
kann Optimierungen auf einer höheren Ebene vornehmen.
OpenSG leitet aus der Szenengraph-Hierarchie eine Bounding Box-Hierarchie ab, um schnell
entscheiden zu können, welche Teil der Szene für den Betrachter sichtbar sind und welche
Teilbäume komplett von der weiteren Berechnung ausgeschlossen werden können.
Der nun auf die sichtbaren Objekte reduzierte Szenengraph wird nach State-Änderungen
sortiert und dann gerendert. Diese Reorganisation des Szenengraphen kann einen
signifikanten Performance-Schub zu Folge haben und die effiziente Ausnutzung der
Grafikhardware als hochparallelisierte Architektur erst ermöglichen. [Ope08]
2.2.2. Multi-Threading
Neue Prozessorgenerationen unterscheiden sich derzeit nicht mehr maßgeblich durch eine
höhere Taktfrequenz von ihren Vorgängern, sondern durch ihre immer mehr auf
Rechenparallelität abzielende Architektur. Die Taktfrequenzen steigen auf Grund des
Erreichens von physikalischen Grenzen (eine höhere Taktfrequenz erfordert auch eine
kleinere Strukturdichte innerhalb des Prozessors) weit weniger stark als noch vor ein paar
Jahren. Um trotzdem leistungsfähigere Prozessoren zu entwickeln werden diese mit immer
mehr Rechenkernen ausgestattet. So sind heute 2-Kern-Prozessoren im Heim- und 4- bis 8-
Kern-Prozessoren im Workstationbereich gängig. Nun stellt sich die Herausforderung
Anwendungen in der Entwicklung so zu konzipieren, dass sie einen größtmöglichen Nutzen
aus der neuen Mehrkern-Hardware ziehen.
Multi-Threading stellt die Softwareentwicklung vor neue Probleme. Jeder Thread rechnet
unabhängig von den anderen und weiß nicht, was diese gerade machen. Aufgrund der
Unabhängigkeit der Prozesse kann nicht vermieden werden, dass ein Prozess schreibend auf
5 Ein Shader ist Programmcode, der für die Grafikberechnung direkt auf der Grafik-Hardware
ausgeführt wird.
6 Unter State-Änderungen fallen z.B. das Wechseln einer Textur, das Ändern der Kameramatrix, …
6
Daten zugreift, während ein anderer lesend auf diese zugreift. Dieses Problem kann
umgangen werden, wenn man das Schreiben auf Daten während der Parallelverarbeitung
verbietet. Das Rendern einer Szene ist eine Operation, die auf diesem Wege realisiert wird.
Die hochparallele Architektur moderner Grafikkarten arbeitet dabei mit einem Datensatz
und verarbeitet diesen parallel (bis zu 320 sogenannter Stream-Prozessoren7) und benötigt
dabei nur lesenden Zugriff. Knotenbasiertes Locking (d.h. schreibt ein Thread auf Daten in
einem Knoten, so ist dieser für andere Threads sowohl lese- als auch schreibgeschützt) ist
eine alternative Methode, um gleichzeitiges Schreiben und Lesen auf den gleichen Daten zu
vermeiden. Dies kann jedoch zu Problemen führen, wenn Threads die Knotenstruktur
verändern (also Kindknoten anhängen / abhängen). Locking kann nur vermieden werden,
wenn jeder Thread eine eigene Kopie der benötigten Daten hat, was aber zu einem sehr
großen Speicherbedarf führen kann.
In einem Szenengraphen gibt es Daten, die zahlreich vorkommen und in Vektor-Strukturen
gespeichert werden, wie Vertices, Normalen, Farben und Texturkoordinaten, und skalare
Daten, wie z.B. die Farbe eines Materials oder eine Transformationsmatrix.
In OpenSG greifen alle Threads auf dieselben Vektor-Daten (sogenannte MultiFields) zu und
jeder Thread verfügt über eine eigene Kopie der skalaren Daten (sogenannte SingleFields).
Somit beträgt der zusätzliche Speicherbedarf nur einen Bruchteil der gesamten Szenengröße.
Wenn ein Thread nun auf einem Vektor aus einem geteilten Speicherbereich schreibt, so
erhält er eine Kopie und schreibt auf dieser Kopie. Der Originalvektor bleibt unangetastet
und wird auch nicht gesperrt. Die Daten der einzelnen Threads müssen jedoch immer wieder
zu einem konsistenten Zustand synchronisiert werden. Dazu werden alle Datenänderungen,
die ein Thread vorgenommen hat in einer Liste verzeichnet und mithilfe der Änderungslisten
der anderen Threads synchronisiert. Dabei werden immer nur die geänderten Daten von
Thread zu Thread kopiert. Da pro Thread meistens nur kleine Datenmengen geändert werden
hält sich der verursachte erhöhte Speicher- und Rechenzeitbedarf in Grenzen. Der
Anwendungsprogrammierer muss OpenSG jedoch immer mitteilen, wenn er im
Programmcode Daten ändert, die zwischen den Threads geteilt werden. Dies geschieht durch
zusätzliche Funktionsaufrufe und man greift auf die Daten über spezielle Zeiger, die auf die
konkrete Kopie der Daten des gerade aktuellen Threads verweisen, zu. [Voß02]
2.2.3. Clustering
Clustering bietet die Möglichkeit, normale PCs mit leistungsfähigen Grafikkarten zu einem
leistungsfähigen und flexibel erweiterbaren Grafiksystem, speziell für VR- und Visuali-
sierungs-Anwendungen, zusammen zu schließen. Diese werden meist über ein
Projektionssystem mit mehreren Projektoren (meist ein Projektor pro Render-PC im Cluster)
sichtbar gemacht.
Auch in diesem Fall stellen sich dieselben Probleme wie im Bereich Multi-Threading. Das
was auf einem PC gemacht wird (Rendern eines Ausschnitts der Szene oder Änderungen an
der Szene) muss den anderen Rechner mitgeteilt werden. Alle Instanzen der Anwendung
müssen auf allen PCs synchronisiert werden.
Aufgrund der Multi-Threading-Eigenschaften speichert OpenSG bereits Änderungen am
Szenengraphen und den zugrundeliegenden Daten pro Thread auf einem einzelnen PC und
damit auch pro PC im Rendercluster. Diese Änderungen müssen nun noch in ein
2.2.4. Erweiterbarkeit
Jede OpenSG-Klasse und jeder OpenSG-Typ kann verändert werden und es ist möglich,
komplett neue Klassen zu schreiben, die sich vollständig in OpenSG integrieren und nicht die
ursprüngliche OpenSG-Bibliothek beeinflussen oder verändern. Dazu setzt OpenSG
fortgeschrittene Programmiermethoden wie Entwurfsmuster (Besucher-, Fabrik- und
Prototyp-Muster) und Reflektion ein. [Ope08]
2.2.5. Multi-Plattform
OpenSG basiert auf plattformunabhängigen Bibliotheken wie OpenGL und Boost und
unterstützt plattformspezifische Fenstersysteme. OpenSG läuft auf Windows-, Linux-, Mac
OSX- und Solaris-Betriebssystemen und auf verschiedenen Plattformen wie PDAs (mit
OpenGL ES), PCs, Clustern und Grafik-Workstations. Gibt es eine OpenGL-Implementierung
auf dem System, so ist theoretisch auch OpenSG lauffähig. [Ope08]
2.3. Ausblick
OpenSG 2 ist derzeit in Entwicklung und soll viele Verbesserungen und Erweiterungen
erfahren. Trotzdem wird die Generalität von OpenSG beibehalten, so dass es auch weiterhin
für ein breites Feld von möglichen Anwendungen genutzt werden kann. Es soll
benutzerfreundlicher werden und die Komplexität eines ausgereiften Grafiksystems vor dem
Benutzer so gut wie möglich verbergen. Die Benutzung von multi-thread-sicheren Daten soll
für den Benutzer vereinfacht werden (Zugriff über normale C-Pointer) und insgesamt
recheneffizienter werden. Multi-Pass-Algorithmen wie Schatten-Berechnung und HDR8-
Rendering sollen besser in das System integriert werden. Dynamisch erzeugte Shader sollen
das Zusammensetzen von Shadern über einzelne Shader-Bausteine, die im Szenengraphen
hinterlegt werden, ermöglichen. Außerdem soll das ganze Render-System im Hinblick auf
den zukünftigen OpenGL-Standard 3.0 ausgelegt werden. Der Quellcode soll durch die
Einbindung eines Unit-Testing-Systems robuster werden. Da OpenSG auf Open Source und
der freiwilligen Arbeit basiert, kann es noch eine Zeitlang dauern bis OpenSG 2 erscheint.
OpenSG 1.8 erschien im Juli 2007 und es sind erst etwa ein Drittel der für OpenSG geplanten
Features implementiert.
3.1. Eingabegeräte-Abstraktion
Konkrete Eingabegeräte setzen sich aus einem oder mehreren abstrahierten Eingabetypen
zusammen. Jeder Typ spezifiziert eine konsistente Schnittstelle, die für alle Eingabegeräte
gleich ist, die diesen Typ implementieren. Folgende Typen sind in VRPN enthalten:
• Tracker sendet seine Position und Orientierung im Raum sowie Geschwindigkeit-
und Beschleunigungswerte.
• Button sendet Drücken- und Loslassen-Ereignisse für einen oder mehrere Buttons.
• Analog sendet einen oder mehrere analoge Werte.
• Dial senden Informationen über fortführende Rotationen.
• ForceDevice ermöglicht der Clientseite, Oberflächen und Kräftefelder
dreidimensional zu spezifizieren.
Um nun ein Eingabegerät zu verwenden, werden die einzelnen Möglichkeiten des Gerätes auf
Eingabetypen abgebildet. So kann man z.B. einen handelsüblichen Joystick mit zwei Tasten
auf zwei Button-Typen und zwei Analog-Typen abbilden. Eine Client-Anwendung geht mit
den Typen einzeln um, sodass es für die Anwendung keine Rolle spielt, ob nun ein Joystick
9
mit zwei Buttons oder eine Maus mit zwei Buttons angeschlossen ist, da sie beide dieselben
Typen implementieren. Somit kann die Anwendung mit allen Eingabegeräten genutzt
werden, die zwei Analogs und zwei Buttons implementieren.
Obwohl die einzelnen Typen eines Eingabegerätes logisch getrennt sind, werden sie trotzdem
auf eine Netzwerkverbindung abgebildet.
Von einem Eingabegerät ausgehend können können mehrere logische Eingabetypen abgeleitet
werden. So kann z.B. ein frei drehbares Rad benutzt werden, um eine Rotation darzustellen
(Typ Dial) oder um einen Wert zu liefern (Typ Analog). Eingabetypen können
könne außerdem in
Schichten angeordnet sein, d.h. die Ausgabe eines Typs kann Eingabe eines anderen sein.
So sendet die vrpn_Joystik-Klasse
Klasse z.B. einen analogen Wert für jede Joystick-Achse.
Joystick Die
vrpn_Analog_Fly-Klasse
Klasse nimmt diese Werte als Eingaben und gibt Tracker-Daten
Tracker aus, um in
VR-Anwendungen
Anwendungen durch die 3D-Szene
3 zu fliegen. Die Client-Seite
Seite kann nun entscheiden, ob
sie die Analog- oder die Tracker-Daten
Tracker oder beide benutzt. [Tay01]
3.2. VRPN-Verbindungsaufbau
Verbindungsaufbau
Der Server öffnet einen festgelegten UDP9-Port
Port für Verbindungsanfragen von Clients. Der
Client öffnet einen verfügbaren
TCP10-Port
Port und sendet eine UDP- UDP
Anfrage an den Server, um eine
Verbindung auf diesem TCP-Port
TCP
aufzubauen. Der Client fragt dann
den TCP-Port
Port ab, ob der Server
geantwortet hat. Wenn die
Verbindung über TCP aufgebaut ist,
werden die Versionen abgeglichen
sowie die Zeit synchronisiert.
Außerdem wird eine separate
separat UDP-
Verbindung zum schnellen
Datenaustausch aufgebaut. Die
eigentlichen Daten der Eingabegeräte
werden dann über UDP ausgetauscht.
Dies hat zur Folge, dass zwar einzelne
Datenpakete verloren gehen können,
aber dafür kann mit einer hohen Abbildung 3 - Anwendungen nutzen ein Eingabegerät
über verschiedene Klassen
Ein
Geschwindigkeit gesendet
esendet werden.
TCP wäre für Echtzeitanwendungen
zu langsam.
Wird die Verbindung durch den Client oder den Server getrennt, so wartet jeweils die andere
Seite auf eine neue Verbindungsaufnahme. Fällt z.B. der Server kurzzeitig aus, so muss die
Client-Anwendung
Anwendung nicht neu gestartet werden, was bei langen Startzeiten der Client- Client
Anwendung sehr vorteilhaft ist. [Tay01]
3.4. Logging
VRPN verfügt über eine Logging-Funktion, die alle Nachrichten in einer Datei speichern
kann. Dabei kann sowohl auf Server- als auch auf Client-Seite mitgeschnitten werden. Ein
Client kann sich mit einer bestehenden Log-Datei verbinden und diese Daten anstelle einer
normalen Serververbindung verarbeiten. Somit ist es möglich, aufgezeichnete Bewegungen /
Benutzereingaben erneut, als eine Art Replay-Funktion, zu verwenden. Es ist dafür keine
Codeänderung nötig.
Weitere Informationen sind unter [VRP08] zu finden.
4. VRPN benutzen
4.1. Der allgemeine VRPN-Server
VRPN liegt ein universeller Server mit dem Namen vrpn_server.exe bei. Ab Version 7.14
wird auch der 3DConnexion SpaceNavigator und SpacePilot unterstützt. Um den VRPN-
Server mit dem SpaceNavigator zu nutzen, öffnet man die vrpn.cfg und entfernt das
Kommentarzeichen in der Zeile #vrpn_3DConnexion_Navigator device0. Man braucht
optional nur noch den Namen von device0 in z.B. SpaceNav ändern und kann dann den
Server starten. Der kompilierte Server befindet sich im Release-Verzeichnis des Projektes.
11
4.2. Einen eigenen Server schreiben
Nachfolgend wird ein einfacher Server für den 3DConnexion SpaceNavigator Schritt für
Schritt erstellt.
In VRPN ist bereits eine Klasse enthalten, die die Funktionalität des SpaceNavigators in
VRPN implementiert. Die Klasse ist in der Datei vrpn_3DConnexion.h definiert und
implementiert die VRPN-Schnittstellen Button und Analog.
Als erstes wird die Headerdatei eingebunden. Und es folgt die Deklaration zweier globaler
Variablen, die Netzwerkverbindung und eine Instanz der SpaceNavigator-Klasse.
#include <vrpn_3DConnexion.h>
vrpn_Connection *connection;
vrpn_3DConnexion_Navigator *nav;
Vor der main()-Funktion wird eine Shutdown-Funktion definiert, die den Referenzzähler der
Verbindung um eins verringert:
void shutdown()
{
if(connection)
connection->removeReference(); connection = NULL;
}
Nun folgt die main()-Funktion. Als erstes wird der Port definiert, über den die
Netzwerkverbindung hergestellt werden soll. Dabei kann die Konstante
vrpn_DEFAULT_LISTEN_PORT_NO benutzt werden, die von VRPN aktuell mit dem Wert 3883
definiert wird. Dieser Port wurde VRPN von der Internet Assigned Numbers Authority
zugewiesen.
int port = vrpn_DEFAULT_LISTEN_PORT_NO;
Nun wird die Verbindung mit dem zuvor festgelegten Port erzeugt.
connection = new vrpn_Connection(port);
Hierbei übergibt man dem Klassenkonstruktor einen String, der einen frei wählbaren
Gerätnamen sowie den Namen des Computers, auf dem der Server läuft beinhaltet, z.B.
„SpaceNav@viswork01“. Als zweiter Parameter wird die eben erstellte Verbindung
übergeben.
Nun kann die Hauptschleife des Servers betreten werden, in der die Hauptschleifen der
SpaceNavigator-Klasse und der Verbindung aufgerufen werden sowie die Anwendung bei
Auftreten eines Fehlers heruntergefahren wird:
while (true)
{
nav->mainloop();
connection->mainloop();
if(!connection->doing_okay())
12
shutdown();
}
Shutdown();
In den Schleifen werden alle Ereignisse verarbeitet und an evtl. verbundene Clients über das
Netzwerk gesendet.
Der komplette Quellcode befindet sich im Projektordner SpaceNavigator/
SpaceNavigatorServer.
Der Server führt die Hauptschleife so oft wie möglich aus. Das führt dazu, dass die
Prozessorbelastung stark steigt, so dass ein Kern des Prozessors voll ausgelastet wird. Ein
Experimentieren mit der Funktion vrpn_SleepMsecs(double dMsecs), die dafür sorgt, dass
die Hauptschleife nur alle dMsecs ausgeführt wird, führte immer zu falschen Ausgaben des
SpaceNavigators (Werte springen hin und her). Für die endgültige Benutzung des
SpaceNavigators ist daher der Einsatz des allgemeinen VRPN-Servers zu empfehlen. Bei
diesem kann über die Kommandozeile der Parameter -millisleeep übergeben werden, wobei
sich Werte bis 5 bewährt haben. Der im Release-Verzeichnis enthaltene Server
(vrpn_server.exe) ist bereits standardmäßig auf 5 Millisekunden eingestellt und muss nicht
unbedingt mit dem Parameter gestartet werden.
In der main()-Funktion wird zuerst eine Instanz der vrpn_Button_Remote-Klasse erzeugt. Als
Parameter wird derselbe String übergeben, wie auch im Server bei der Erstellung der
SpaceNavigator-Klasse angegeben wurde, also z.B. „SpaceNav@viswork01“.
vrpn_Button_Remote *button = new vrpn_Button_Remote("SpaceNav@viswork01");
Anschließend wird eine Callback-Funktion für den Button registriert. Diese wird immer
aufgerufen, wenn eine Nachricht vom Server über einen veränderten Zustand eines Buttons
eintrifft:
button->register_change_handler(NULL, (vrpn_BUTTONCHANGEHANDLER)handleButtons);
13
button->mainloop();
analog->mainloop();
}
In der Funktion steht nun über den Parameter buttonData der Zustand des aktuell
veränderten Buttons bereit. buttonData.button enthält die Buttonnummer und
buttonData.state enthält den Zustand. Dabei steht 1 für gedrückt und 0 für nicht gedrückt.
Diese Information kann nun auf der Konsole ausgegeben werden:
printf("Button: %d, Button state: %d\n\n", buttonData.button, buttonData.state);
Die Werte liegen dabei im Bereich [-1, 1] und werden in diesem Beispiel auf den Bereich [-
1000, 1000] skaliert.
Der komplette Quellcode ist im Projektordner SpaceNavigator/SpaceNavigatorClient zu
finden.
4.4. SpaceNavigator-Klasse
Aufbauend auf dem vorgestellten VRPN-Client wird eine SpaceNavigator-Klasse entwickelt.
Diese soll folgende Anforderungen erfüllen:
1. Implementierung als VRPN-Client
2. Implementierung mithilfe des Singleton-Entwurfsmusters
3. Initialisierung und Aufbauen einer Verbindung zum VRPN-Server
a. Setzen der nach oben zeigenden Achse (Y – normal, Z – für Anwendungen aus
den Geowissenschaften)
4. Bereitstellen des aktuellen Zustands des SpaceNavigators
a. Bereitstellen der Translationswerte
b. Bereitstellen der Rotationswerte
c. Bereitstellen des Zustands der Buttons
5. Verschiedene Funktionsmodi:
a. Translation und Rotation
b. Nur Translation
c. Nur Rotation
6. Achsenbeschränkung
a. Nur Achse mit höchstem Ausschlag relevant
b. Kombinierbar mit den Funktionsmodi
7. Invertierung der Achsen
8. Standard-Buttonbelegung
14
Folgendes Listing zeigt die Headerdatei der SpaceNavigatorClient-Klasse:
#pragma once
#include <vrpn_Button.h>
#include <vrpn_Analog.h>
class SpaceNavigatorClient
{
public:
enum SpaceNavigatorMode
{
TRANSROT = 0,
TRANS,
ROT
};
enum SpaceNavigatorAxes
{
X = 1,
Y,
Z,
rX,
rY,
rZ
};
bool buttons[8];
float x, y, z;
float rx, ry, rz;
protected:
SpaceNavigatorClient();
~SpaceNavigatorClient();
private:
vrpn_Button_Remote *_button;
vrpn_Analog_Remote *_analog;
SpaceNavigatorAxes _upAxis;
bool _dominating;
SpaceNavigatorMode _mode;
bool _defaultButtonBehaviour;
float _invertAxes[6];
static SpaceNavigatorClient* _spacenavigator;
15
4.4.1. Implementierung als VRPN-Client
Analog zum VRPN-Client-Beispiel verfügt die Klasse über jeweils ein vrpn_Button_Remote-
und ein vrpn_Analog_Remote-Objekt sowie über die zugehörigen Callback-Funktionen, die
immer aufgerufen werden, wenn der Client eine neue Nachricht vom Server über einen
veränderten Zustand des Eingabegerätes erhält.
return _spaceNavigator;
}
if(axis == Z)
upAxis = axis;
else
upAxis = Y;
}
16
4.4.4. Bereitstellen des aktuellen Zustands des SpaceNavigators
Der aktuelle Zustand des SpaceNavigators wird in folgenden öffentlichen Variablen
Variabl
gespeichert:
bool buttons[2];
float x, y, z;
float rx, ry, rz;
…
}
vrpn_ANALOGCB analogData
channel[0] channel
channel[2] channel[1] channel[3] channel[5] channel[4]
SpaceNavigatorClient
x y z rx ry rz
SpaceNavigatorClient::
SpaceNavigatorClient::invertAxes[]
normal invertiert normal normal invertiert normal
In der Analog-Callback-Funktion
Funktion werden ebenso die einzelnen Werte in den öffentlichen
Variablen gespeichert:
void CALLBACK handleAnalogs(void *, vrpn_ANALOGCB analogData)
{
spacenavigator->x
>x = analogData.channel[0] * spacenavigator->invertAxes[0];
>invertAxes[0];
spacenavigator->y
>y = analogData.channel[2] * spacenavigator->invertAxes[1];
spacenavigator >invertAxes[1];
spacenavigator->z
>z = analogData.channel[1] * spacenavigator->invertAxes[2];
spacenavigator >invertAxes[2];
spacenavigator->rx
>rx = analogData.channel[3] * spacenavigator->invertAxes[3];
spacenavigator >invertAxes[3];
spacenavigator->ry
>ry = analogData.channel[5] * spacenavigator->invertAxes[4];
spacenavigator >invertAxes[4];
spacenavigator->rz
>rz = analogData.channel[4] * spacenavigator->invertAxes[5];
spacenavigator >invertAxes[5];
}
17
In der nichtöffentlichen Variable invertAxes ist für jede Achse gespeichert, ob sie invertiert
werden soll. In Abbildung 4 wird die Zuordnung der Space Navigator-Achsen verdeutlicht.
Es ist standardmäßig der erste Modus aktiv. Der aktive Modus kann mit den Funktionen
switchMode() und setMode(SpaceNavigatorMode mode) gesetzt werden.
4.4.6. Achsenbeschränkung
Es ist möglich, die Werte des Navigators auf eine Achse zu beschränken. Es wird dann nur
die Achse betrachtet, die den höchsten Absolutwert hat. Damit kann man genauer mit dem
SpaceNavigator navigieren. Die Funktionen switchDomination() und setDomination(bool
dominating) schalten diese Funktion an und aus. Am Beispiel des Standard-Funktions-
Modus (Translation und Rotation) wird im Folgenden die Implementierung der
Achsenbeschränkung gezeigt:
if(spacenavigator->dominating)
{
float max = analogData.channel[0];
int index = 0;
for(int i = 1; i < 6; i++)
{
if(abs(analogData.channel[i]) > abs(max))
{
index = i;
max = analogData.channel[i];
}
}
spacenavigator->x = spacenavigator->y = spacenavigator->z = 0;
spacenavigator->rx = spacenavigator->ry = spacenavigator->rz = 0;
switch(index)
{
case 0: spacenavigator->x = max * spacenavigator->invertAxes[0]; break;
18
case 2: spacenavigator->y = max * spacenavigator->invertAxes[1]; break;
case 1: spacenavigator->z = max * spacenavigator->invertAxes[2]; break;
case 3: spacenavigator->rx = max * spacenavigator->invertAxes[3]; break;
case 5: spacenavigator->ry = max * spacenavigator->invertAxes[4]; break;
case 4: spacenavigator->rz = max * spacenavigator->invertAxes[5]; break;
}
}
4.4.8. Standard-Buttonbelegung
Die beiden Knöpfe des SpaceNavigators sind mit folgenden Funktionen belegt:
• Linker Knopf: Wechseln des Funktionsmodus
• Rechter Knopf: Wechseln der Achsenbeschränkung
Diese Belegung kann mit der Funktion setDefaultButtonBehaviour(bool enable) aus- und
eingeschaltet werden. Die Belegung wird in der Button-Callback-Funktion implementiert:
if(spacenavigator->defaultButtonBehaviour)
{
if(spacenavigator->buttons[0])
spacenavigator->switchMode();
if(spacenavigator->buttons[1])
spacenavigator->switchDomination();
}
19
Außerdem benötigt man zum Zugriff auf die Objektinstanz der Klasse einen Zeiger:
SpaceNavigatorClient* spaceNavigator;
Bevor man auf die Werte des SpaceNavigators zugreifen kann, muss erst die Hauptschleife
der Klasse ausgeführt werden, z.B. in der GLUT-Display-Funktion:
spaceNavigator->mainloop();
Anschließend kann auf die aktuellen Werte über die öffentlichen Variablen der Klasse
zugegriffen werden, z.B.:
float bewegungXAchse = spaceNavigator->x;
Alternativ kann man auch immer über die statische Instance()-Funktion auf die Klasse
zugreifen. In diesem Fall benötigt man keinen Zeiger auf die Klasse.
float bewegungXAchse = SpaceNavigatorClient::Instance()->x;
#include "../SpaceNavigator/SpaceNavigatorClient.h"
Um nicht immer den Namensraum osg:: vor jedes OpenSG-Objekt schreiben zu müssen,
verwendet man den OpenSG-Namensraum:
OSG_USING_NAMESPACE
Nun werden die globalen Variablen deklariert. Es wird ein Zeiger auf die Instanz der Space-
Navigator-Klasse, auf den SimpleSceneManager sowie auf ein Transform-Objekt, indem
später die Transformation des Würfels gespeichert wird, angelegt.
20
SpaceNavigatorClient* spaceNavigator;
SimpleSceneManager* mgr; Node n
TransformPtr tMain;
Die Setup-Funktion
Funktion von GLUT muss vor den anderen Group::create()
Funktionen des Programms deklariert werden.
int setupGLUT(int *argc, char *argv[]); Node
mainTrans
Anschließend wird in der createScene()-Funktion der
Würfel mitsamt zugehöriger Transformation erstellt. Transform tMain
Die Funktion gibt einen NodePtr zurück, an den die
Transformation sowie der Würfel angehängt sind.
Matrix m
Zuerst wird eine Matrix-Variable
Variable angelegt:
NodePtr createScene(void)
Node box
{
Matrix m;
Nun erzeugt man den Wurzelknoten des Szenengraphen und hängt den zuletzt erzeugten
Knoten als Kind an.
NodePtr n = Node::create();
beginEditCP(n, Node::CoreFieldMask | Node::ChildrenFieldMask);
Node::ChildrenFieldMa
n->setCore(Group::create());
>setCore(Group::create());
n->addChild(mainTrans);
>addChild(mainTrans);
endEditCP(n, Node::CoreFieldMask
Node::CoreFieldM | Node::ChildrenFieldMask);
Damit ist der Szenengraph (siehe Abbildung 5) vollständig erstellt. Wie bereits erwähnt,
kümmert sich der SimpleSceneManager um die Erstellung und Positionierung einer Kamera.
In der main()-Funktion
Funktion wird als erstes das SpaceNavigator-Objekt
SpaceNavigator Objekt erstellt und initialisiert.
Zur Initialisierung muss der Name des SpaceNavigators auf dem Server und der
Rechnername des Servers als Kommandozeilen-Parameter
Parameter übergeben werden:
werden
21
int main(int argc, char **argv)
{
spaceNavigator = SpaceNavigatorClient::Instance();
spaceNavigator->init(connectionString);
char *connectionString;
if(argc > 1)
connectionString = argv[1];
spaceNavigator->init(connectionString);
osgInit(argc, argv);
Der SimpleSceneManager wird erzeugt. An ihn werden das GLUT-Fenster sowie der
Wurzelknoten der Szene übergeben. Die Funktion showAll() positioniert die Kamera
automatisch so, dass der Würfel sichtbar ist.
mgr = new SimpleSceneManager;
mgr->setWindow(gwin);
mgr->setRoot(scene);
mgr->showAll();
mgr->setStatistics(true);
Schließlich startet man die GLUT-Hauptschleife und übergibt somit GLUT die Kontrolle über
die Anwendung.
glutMainLoop();
return 0;
}
Man kann jedoch Callback-Funktionen definieren, die von GLUT bei speziellen Ereignissen
aufgerufen werden. Die Display-Funktion ist hierbei sicherlich die wichtigste. In dieser kann
die Szene verändert und schließlich gerendert werden. In dieser Anwendung wird also die
Box über den TransformPtr mithilfe der Eingaben des SpaceNavigator bewegt und rotiert und
am Ende gerendert. In der Display-Funktion wird erst die Hauptschleife des SpaceNavigators
ausgeführt:
void display(void)
{
spaceNavigator->mainloop();
Es werden einige Variablen für die folgenden Anweisungen angelegt. Über die beiden
Gleitkommavariablen transFactor und rotFactor kann die Sensitivität gesteuert werden.
Matrix t, r, old;
float transFactor = 1.f;
float rotFactor = 0.05f;
Vec3f oldTrans, oldScale;
Quaternion oldRot, oldScaleRot;
22
Die bisherige Transformation des Würfels wird mithilfe der Matrix-Funktion getTransform()
in den Hilfsvariablen gespeichert. Diese Transformation ist in der globalen Transform-
Variablen gespeichert.
tMain->getMatrix().getTransform(oldTrans, oldRot, oldScale, oldScaleRot);
In der Matrix old wird nun eine Transformation erzeugt, die der alten Translation
entspricht.
old.setTransform(oldTrans);
In der Matrix t wird die neue Translation erzeugt. Dabei werden die Werte des
SpaceNavigators ausgelesen und mit dem Transformationsfaktor multipliziert:
t.setTranslate(spaceNavigator->x * transFactor, spaceNavigator->y * transFactor,
spaceNavigator->z * transFactor);
Nun werden die neuen Rotationen erzeugt. Dazu wird jeweils ein Quaternion erzeugt,
welches die Rotation um eine Achse darstellt und auf den jeweiligen Rotationswert des
SpaceNavigators zurückgreift. Entsprechend fließt hier der Rotationsfaktor mit ein. Die drei
Quaternions für die drei Rotationen werden miteinander multipliziert, was einer Verkettung
der einzelnen Rotationen entspricht.
Quaternion rot=Quaternion(Vec3f(0,0,1), (3.14159*spaceNavigator->rz)*rotFactor);
rot.mult(Quaternion(Vec3f(0,1,0), (3.14159 * spaceNavigator->ry ) * rotFactor));
rot.mult(Quaternion(Vec3f(1,0,0), (3.14159 * spaceNavigator->rx ) * rotFactor));
Die resultierende Rotation wird mit der alten Rotation multipliziert und in der Matrix r wird
die finale Rotationsmatrix erzeugt:
rot.mult(oldRot);
r.setRotate(rot);
Schließlich wird die Transformationsmatrix des Transform-Objekts der Box auf die finale
Transformationsmatrix gesetzt:
beginEditCP(tMain);
tMain->setMatrix(old);
endEditCP(tMain);
Nun muss nur noch die anfangs erwähnte setupGLUT()-Funktion implementiert werden. Die
GLUT-Initialisierung wird aufgerufen und der Displaymodus festgelegt.
int setupGLUT(int *argc, char *argv[])
{
glutInit(argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
Mit glutCreateWindow() wird das eigentliche Programmfenster erzeugt. Als Parameter wird
der Fenstername übergeben.
23
int winid = glutCreateWindow("OpenSG
First App");
return winid;
}
#include <OpenSG/OSGGLUT.h>
#include <OpenSG/OSGSimpleSceneManager.h>
#include <OpenSG/OSGSimpleMaterial.h>
#include <OpenSG/OSGMaterialGroup.h>
#include <OpenSG/OSGComponentTransform.h>
#include "../SpaceNavigator/SpaceNavigatorClient.h"
#include "../TriangleElevationGrid/TriangleElevationGrid.h"
OSG_USING_NAMESPACE
protected:
void SpaceNavigatorSSM::initialize();
private:
SpaceNavigatorClient _spaceNavigator;
float _translationFactor;
float _rotationFactor;
bool _zUpAxis;
bool _heightControl;
float _groundDistance;
bool _objectPicking;
osg::NodePtr _pickedObjectNode;
int _elapsedTime;
CElevationGridBase *_grid;
Bool _useElevationGrid;
};
25
Die Variable _spaceNavigator ist die Instanz der Klasse SpaceNavigatorClient, sie bietet also
Zugriff auf das SpaceNavigator-Eingabegerät. Die Variable _rotationFactor ist eine
Gleitkommazahl, über die die Empfindlichkeit der Rotationseingaben festgelegt werden
kann. Analog wird _translationFactor für die Empfindlichkeit der Translation verwendet. In
_elapsedTime wird die seit dem Initialisieren des GLUT-Systems vergangene Zeit gespeichert.
Nun folgen einige Statusvariablen. In ihnen wird gespeichert, ob die Z-Achse die nach oben
zeigende Achse ist (_zUpAxis), ob der Abstand zum Boden gleichbleibend oder über eine
Achse des SpaceNavigators gesteuert werden kann (_heightControl) und ob man mit der
Maus Objekte selektieren kann (_objectPicking). Ein Verweis auf die Transformation des
ausgewählten Objektes wird in _pickedObjectNode gespeichert. Die aktuelle Höhe der
Kamera über dem Boden wird in der Variable _groundDistance gespeichert. In _grid wird
eventuell eine spezielle Struktur zur Kollisionserkennung gespeichert und die Variable
_useElevationGrid bestimmt, ob diese Struktur zur Kollisionsabfrage genutzt wird (siehe
auch 4.5.7).
Im Folgenden wird die eigentliche Implementierung der Klasse erklärt.
Dem Konstruktor wird der Verbindungsstring des SpaceNavigator-Eingabegerätes (so wie er
auch im SpaceNavigator-Server angegeben wurde) und optional, ob die Z-Achse als nach
oben zeigende Achse benutzt werden soll, übergeben.
SpaceNavigatorSSM::SpaceNavigatorSSM(char *deviceName, bool zUpAxis)
{
Entsprechend dem zUpAxis-Parameter wird der SpaceNavigator initialisiert und die private
Variable _zUpAxis gesetzt.
if(zUpAxis)
SpaceNavigatorClient::Instance()->init(deviceName, SpaceNavigatorClient::Z);
else
SpaceNavigatorClient::Instance()->init(deviceName);
_zUpAxis = zUpAxis;
Die restlichen Variablen werden ebenfalls initialisiert. Die Bodendistanz wird auf 2 und der
Rotationsfaktor auf 1 gesetzt. Die Höhe über dem Boden wird auf fest eingestellt und der
Objektauswahlmodus aktiviert:
_groundDistance = 2.0f;
_rotationFactor = _translationFactor = 1.f;
_heightControl = false;
_objectPicking = true;
_grid = NULL;
_useElevationGrid = false;
}
26
Abbildung 7 – links: OpenSG-Koordinatensystem (Y-Achse ist Hochachse), rechts: rechtshän-
diges Koordinatensystem mit Z-Achse als Hochachse
SimpleSceneManager::setRoot(root);
if(this->_zUpAxis)
this->getNavigator()->setUp(Vec3f(0, 0, 1));
}
Außerdem wird in der setRoot()-Methode die ebenfalls geerbte und überschrieben Methode
initialize() aufgerufen. Diese ruft einerseits die originale Initialize-Methode der
Basisklasse auf und setzt andererseits den Modus der Navigator-Klasse auf Navigator::WALK.
void SpaceNavigatorSSM::initialize()
{
SimpleSceneManager::initialize();
this->getNavigator()->setMode(Navigator::WALK);
}
Dieser WALK-Modus implementiert das „Laufen“ über einen festzulegenden Boden. Man
befindet sich mit der Kamera dann immer in einem festen Abstand zum Boden und kann sich
somit z.B. frei auf einer Landschaft bewegen und die Kamera folgt dabei dem Geländeprofil.
Die ebenfalls geerbte showAll()-Methode wird nur dahingehend abgeändert, dass nun auch
die Hoch-Achse berücksichtigt wird. Im Folgenden werden nur die Änderungen gezeigt.
void SpaceNavigatorSSM::showAll(void)
{
…
Real32 dist;
if(_zUpAxis)
dist = osgMax(d[0],d[2]) / (2 * osgtan(_camera->getFov() / 2.f));
else
dist = osgMax(d[0],d[1]) / (2 * osgtan(_camera->getFov() / 2.f));
Vec3f up = getNavigator()->getUp();
Pnt3f at;
27
if(_zUpAxis)
at = Pnt3f((min[0] + max[0]) * .5f,(min[2] + max[2])*.5f,(min[1]+max[1])*.5f);
else
at = Pnt3f((min[0] + max[0]) * .5f,(min[1] + max[1])*.5f,(min[2]+max[2])*.5f);
Pnt3f from=at;
if(_zUpAxis)
from[1]+=(dist+fabs(max[1]-min[1])*0.5f);
else
from[2]+=(dist+fabs(max[2]-min[2])*0.5f);
_navigator.set(from,at,up);
…
}
Die Variablen min und max beinhalten die Ausdehnung der Szene. Die Variable d ist die
Differenz von min und max.
Anhand der berechneten Zeit und der gespeicherten Faktoren wird die Empfindlichkeit der
Translation und Rotation des SpaceNavigators angepasst.
float transFactor = _translationFactor * (frameTime / 1000.0);
float rotFactor = _rotationFactor * (frameTime / 800.0);
float rotFactorObject = rotFactor * 3.0f;
Ein Zeiger auf den WalkNavigator der Basisklasse wird unter dem Namen wnav gespeichert:
WalkNavigator* wnav = _navigator.getWalkNavigator();
28
switch(this->getNavigator()->getMode())
{
case Navigator::WALK:
…
case Navigator::TRACKBALL:
break;
}
}
Im WALK-Modus wird ebenso unterschieden, ob gerade ein Objekt ausgewählt ist (siehe
4.5.4) oder die Kamera mit den Eingaben des SpaceNavigators gesteuert wird.
if(_pickedObjectNode != NullFC)
{
…
}
else
{
…
}
break;
Gegebenenfalls wird noch die Höhe der Kamera über dem Boden mithilfe der am
SpaceNavigator gemachten Eingabe aktualisiert. Hierbei werden die SpaceNavigator-Achsen
wieder entsprechend der Hoch-Achse verwendet. Außerdem wird sichergestellt, dass man
nicht unterhalb des Bodens gelangen kann:
if(_heightControl)
{
if(_zUpAxis)
_groundDistance += spaceNavigator->z * transFactor;
else
_groundDistance += spaceNavigator->y * transFactor;
if(_groundDistance < 0.1f) _groundDistance = 0.1f;
wnav->setGroundDistance(_groundDistance);
}
}
29
4.5.4. Auswählen von Objekten per Mausklick
Ein Objekt soll beim Drücken der linken Maustaste ausgewählt werden und beim Drücken
der rechten Maustaste soll die Auswahl beendet werden. Um das ausgewählte Objekt wird für
eine bessere Sichtbarkeit eine Umgebungsbox (bounding box) eingeblendet. Die von der
Basisklasse geerbte Methode mouseButtonPress() wird überschrieben. Diese Methode wird
immer aufgerufen wenn eine Maustaste gedrückt wurde.
void SpaceNavigatorSSM::mouseButtonPress(UInt16 button, Int16 x, Int16 y)
{
Wurde die linke Maustaste gedrückt und ist der Objektauswahlmodus aktiv, so wird ein
Strahl ausgehend vom angeklickten Pixel in die Szene gesendet und damit ein Intersection-
Objekt gebildet. Nun wird getestet, ob der Strahl mit einem Objekt in der Szene kollidiert.
Dazu wird die apply()-Methode aufgerufen und ihr der Wurzelknoten des Szenengraphen
übergeben. Wenn ein Objekt getroffen wurde wird ein Verweis darauf in der Variablen
_pickedObjectNode gespeichert.
switch (button)
{
case MouseLeft:
if(_objectPicking)
{
Line ray = calcViewRay(x, y);
IntersectAction *iAct = IntersectAction::create();
iAct->setLine(ray);
iAct->apply(this->getRoot());
if(iAct->didHit())
{
_pickedObjectNode = iAct->getHitObject();
Nun wird der Szenengraph ausgehend vom getroffen Objekt nach oben durchlaufen, bis man
auf einen Transformations-Knoten stößt. Dieser wird wiederum in _pickedObjectNode
gespeichert. In der Variablen wird also nicht auf das Objekt selber, sondern auf den
zugehörigen Transformationsknoten verwiesen. Sind z.B. mehrere Objekte an einen
Transformationsknoten angehängt, so werden nach diesem Verfahren auch alle Objekte
ausgewählt.
while(!_pickedObjectNode->getCore()->getType().
isDerivedFrom(Transform::getClassType()))
{
if(_pickedObjectNode->getParent() != this->getRoot())
_pickedObjectNode = _pickedObjectNode->getParent();
Wurde der ganze Szenengraph bis zum Wurzelknoten durchlaufen und es wurde kein
Transformations-Knoten gefunden, so wird ein neuer Transformations-Knoten erzeugt und
an Stelle des gewählten Objektes in den Szenengraphen eingefügt. Das gewählte Objekt wird
als Kind an den neuen Transformations-Knoten angehängt.
else
{
NodePtr pickedObject = iAct->getHitObject();
TransformPtr newTransform = Transform::create();
Matrix m;
m.setIdentity();
beginEditCP(newTransform, Transform::MatrixFieldMask);
newTransform->setMatrix(m);
endEditCP(newTransform, Transform::MatrixFieldMask);
30
NodePtr newTransformNode = Node::create();
beginEditCP(newTransformNode, Node::CoreFieldMask);
newTransformNode->setCore(newTransform);
endEditCP(newTransformNode, Node::CoreFieldMask);
addRefCP(pickedObject);
beginEditCP(pickedObjectParent);
pickedObjectParent->replaceChildBy(pickedObject,
newTransformNode);
endEditCP(pickedObjectParent);
beginEditCP(newTransformNode);
newTransformNode->addChild(pickedObject);
endEditCP(newTransformNode);
subRefCP(pickedObject);
_pickedObjectNode = newTransformNode;
}
Die Umgebungsbox wird mit der Methode setHighlight() der Basisklasse angezeigt. Die
Umgebungsbox gehört zum ausgewählten Transformationsknoten und ist so groß wie alle als
Kindknoten angehängten Objekte zusammen. Im Normalfall entspricht die Umgebungsbox
aber der Umgebungsbox des ausgewählten Objekts.
this->setHighlight(_pickedObjectNode);
}
}
case MouseUp:
_navigator.buttonPress(Navigator::UP_MOUSE,x,y);
break;
case MouseDown:
_navigator.buttonPress(Navigator::DOWN_MOUSE,x,y);
break;
Wird die rechte Maustaste gedrückt, so wird die Variable _pickedObjectNode auf den Null-
Zeiger gesetzt, die Anzeige der Umgebungsbox wieder ausgeschaltet und der
Maustastendruck weitergeleitet:
case MouseRight:
if(_objectPicking)
_pickedObjectNode = NullFC;
31
this->setHighlight(NullFC);
_navigator.buttonPress(Navigator::RIGHT_MOUSE,x,y);
break;
}
Anschließend wird die Rotation der Kamera in Weltkoordinaten in der Matrix camToWorld
gespeichert.
Matrix camToWorld = getCamera()->getBeacon()->getToWorld();
camToWorld.getTransform(dummy1, rotation, dummy2, dummy3);
camToWorld.setIdentity();
camToWorld.setRotate(rotation);
Nun wird die Rotation des Objektes in Weltkoordinaten in der Matrix objectToWorld
gespeichert und transponiert, also die inverse gebildet, da die Rotationsmatrix orthogonal ist.
Matrix objectToWorld = _pickedObjectNode->getToWorld();
objectToWorld.getTransform(dummy1, rotation, dummy2, dummy3);
objectToWorld.setIdentity();
objectToWorld.setRotate(rotation);
objectToWorld.transpose();
32
Vec3f dvobject;
Nun wird eine Transformationsmatrix camToObject erstellt, die eine Transformation von
Kamera- in Objektkoordinaten repräsentiert. Dazu wird die transponierte Objekt zu Welt-
Rotation (objectToWorld) mit der Kamera zu Welt-Rotation (camToWorld) multipliziert.
Matrix camToObject;
camToObject.setIdentity();
camToObject.mult(objectToWorld);
camToObject.mult(camToWorld);
Vec3f objectRE1;
Vec3f objectRE2;
Vec3f objectRE3;
camToObject.mult(cameraRE1, objectRE1);
camToObject.mult(cameraRE2, objectRE2);
camToObject.mult(cameraRE3, objectRE3);
Nun werden die einzelnen Rotationen um die drei Achsen mithilfe der Eingaben des
SpaceNavigators, der transformierten Vektoren und in Abhängigkeit zur Hoch-Achse in
Quaternions erstellt.
Quaternion qx(objectRE1, spaceNavigator->rx * rotFactorObject);
Quaternion qy, qz;
if(_zUpAxis)
{
qy.setValueAsAxisRad(objectRE2, spaceNavigator->rz * rotFactorObject);
qz.setValueAsAxisRad(objectRE3, spaceNavigator->ry * rotFactorObject);
}
else
{
qy.setValueAsAxisRad(objectRE2, spaceNavigator->ry * rotFactorObject);
qz.setValueAsAxisRad(objectRE3, spaceNavigator->rz * rotFactorObject);
}
Die Quaternions werden miteinander und mit der alten Rotation des Objektes durch
multiplizieren verknüpft.
Matrix transform = (TransformPtr::dcast(_pickedObjectNode->getCore()))
->getMatrix();
transform.getTransform(dummy1, rotation, dummy2, dummy3);
rotation.mult(qx);
rotation.mult(qy);
rotation.mult(qz);
Die finale Transformationsmatrix transform wird durch die Verknüpfung alte Transforma-
tion * neue Verschiebung * neue Rotation in Objektkoordinaten erzeugt.
Matrix m;
m.identity();
33
m.setTranslate(dvobject);
transform.mult(m);
transform.setRotate(rotation);
34
die Zellenanzahl sind daher stark von der Bodengeometrie abhängig. Dieses Verfahren
funktioniert allerdings bisher nur mit Geometrie, bei der die Z-Achse die Hoch-Achse ist.
nav->setFrom(position);
Über set/switchHeightControl kann man die Steuerung der Höhe über den SpaceNavigator
aktivieren. Über set/switchObjectPicking kann der Objektauswahlmodus aktiviert werden.
Der Rotationsfaktor kann über setRotationFactor gesetzt werden. Der Translationsfaktor
kann über setTranslationFactor gesetzt werden. Die Methode setDefaultButtonBehaviour()
legt die Funktionalität der beiden Buttons des SpaceNavigators fest (siehe Kapitel 4.4.8).
Der gesamte Quellcode der SpaceNavigatorSSM-Klasse ist im Projektordner
SpaceNavigator/SpaceNavigatorSSM zu finden.
Außerdem benötigt man einen (globalen) Zeiger auf die Instanz der Klasse.
SpaceNavigatorSSM *mgr;
Im Folgenden wird nur die main()- und die keyboard()-Funktion erklärt. Der komplette
Quellcode ist im Projektordner SpaceNavigator/SpaceNavigatorTestSSM zu finden.
Zu Begin der main()-Funktion wird OpenSG initialisiert und ein GLUT-Fenster erzeugt.
int main(int argc, char **argv)
{
osgInit(argc,argv);
Die Szene, bestehend aus einem virtuellen Straßenabschnitt, wird aus einer Datei gelesen.
NodePtr scene = SceneFileHandler::the().read("../osg/City ohne.osb");
Der Szenenmanager wird erzeugt und der Variablen zugewiesen. Der Verbindungsstring zum
SpaceNavigator wird dabei von der Kommandozeile gelesen.
char* connectionString;
if(argc > 1)
connectionString = argv[1];
mgr = new SpaceNavigatorSSM(connectionString, false);
35
Dem Manager werden das GLUT-Fenster und der Wurzelknoten der Szene übergeben.
mgr->setWindow(gwin );
mgr->setRoot (scene);
Die Bodenkollision des WalkNavigators wird aktiviert. Dabei wird der Initialisierungs-
funktion der Knoten mit dem Namen „ground“ übergeben und die Höhe über Grund auf 5
gesetzt.
mgr->initWalkNavGroundCollision(mgr->getNodeByName(scene, „ground“));
mgr->setGroundDistance(5.0f)
Die Kamera wird automatisch mit der Methode showAll() platziert und ausgerichtet sowie
die Standardbutton-Belegung des SpaceNavigators aktiviert.
mgr->showAll();
mgr->setDefaultButtonBehaviour(true);
return 0;
}
In der keyboard()-Methode wird die Tastenbelegung festgelegt. Mit ´T´ ändert man den
Navigationsmodus auf Trackball, wodurch man die Kamera mit Hilfe der Maus um die Szene
rotieren kann. ´W´ schaltet zurück in den Walk-Modus. Mit ´H´ aktiviert und deaktiviert
man die Höhenverstellung.
Befindet man sich im Walk-Modus, kann man mit dem Mauszeiger und der linken Maustaste
Objekte auswählen und sie dann mit dem SpaceNavigator verschieben und drehen (siehe
Abbildung 8). Per Rechtsklick wird das Objekt wieder deselektiert und man kann sich
mithilfe des SpaceNavigators wieder durch die Szene bewegen.
Abbildung 8 - Ein Objekt wird mithilfe der Maus ausgewählt und mit dem SpaceNavigator gedreht
und angehoben
Das Modell wurde mit der Z-Achse als Hoch-Achse modelliert. Deswegen wird als zweiter
Parameter bei der Erzeugung des SceneManagers true übergeben.
36
mgr = new SpaceNavigatorSSM(computerName, true);
Schließlich werden die Höhe über dem Boden, die Empfindlichkeit der Bewegung und
Rotation sowie die Startposition der Kamera an die Szene angepasst.
mgr->setGroundDistance(20);
mgr->setTranslationFactor(0.2f);
mgr->setRotationFactor(0.5f);
mgr->showAll();
mgr->setCameraPosition(Vec3f(-50, 0, 50));
Alternativ kann man die Anwendung auch über die Konsole starten und den entsprechenden
Verbindungsstring übergeben oder die .bat-Datei editieren und einen anderen Computer
eintragen.
Nun wartet der Server auf eine Verbindungsanfrage von einer Anwendung.
37
4.6.2. Die Testanwendungen (SpaceNavigator-Client) einrichten
Die Testanwendungen können auf dem Display-PC installiert werden, indem einfach der
Ordner Client von der Abgabe-CD in ein Verzeichnis auf der Festplatte kopiert wird. Eine
Anwendung wird über die entsprechende .bat-Datei aufgerufen.
Außerdem ist es wichtig, dass sich die beiden verwendeten Rechner in derselben Netzwerk-
Arbeitsgruppe befinden (im VR-Pool ist dies WORKGROUP für den Display- und den EOS-
Tracking-Rechner und ARBEITSGRUPPE für die restlichen PCs).
Wird der SpaceNavigator an den Rechner vor dem Display angeschlossen, so müsste die .bat-
Datei folgendermaßen aussehen:
SpaceNavigatorTestSSM.exe SpaceNav@eos-ir-tracking
Nachdem der Server gestartet wurde, kann auch die Anwendung gestartet werden.
5. CD-Inhalt
5.1. Ordner Client
Dieser Ordner enthält die Dateien, die im VR-Pool auf den Display-Rechner kopiert werden
müssen.
38
5.4. Ordner SpaceNavigator
Dieser Ordner enthält die Projektordner der einzelnen Klassen und Beispielprogramme, die
Projektdatei (SpaceNavigator.sln) sowie die kompilierte Projekte (Ordner Debug und
Release). Im Unterordner vrpn sind die geänderten VRPN-Quellcode-Dateien zu finden.
39
Literaturverzeichnis
[Gam95] Gamma, Erich und al., et. 1995. Design Patterns - Elements of Reusable
Object-Oriented Software. s.l. : Addison Wesley, 1995.
[Ope08] 2008. OpenSG-Homepage. [Online] Januar 2008. http//opensg.vrsource.org.
[Rot05] Roth, Marcus. 2005. Paralle Bildberechnung in einem Netzwerk von
Workstations. Disertation. Darmstdt : Technische Universität Darmstadt, 2005.
[Tay01] Taylor, Russel M. und al., et. 2001. VRPN: A Device-Independent, Network-
Transparent VR Peripheral System. University of North Carolina at Chapel Hill : s.n., 2001.
[Voß02] Voß, G., et al. 2002. A Multi-thread Safe Foundation for Scene Graphs and its
Extensions to Clusters. Fourth Eurographics Workshop on Parallel Graphics and
Visualization. 2002, S. 33-37.
[VRP08] 2008. VRPN-Homepage. [Online] Januar 2008.
http://www.cs.unc.edu/Research/vrpn/.
40