40
HOCHSCHULE FÜR TECHNIK, WIRTSCHAFT UND KULTUR LEIPZIG Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten für auf OpenSG- basierende Anwendungen in der 3D- Visualisierung Ein Master-Projekt am Helmholtz-Zentrum für Umweltforschung GmbH - UFZ B.Sc. Lars Bilke 28.02.2008 Betreuer: Dr. Björn Zehner (UFZ) Prof. Dr. Ing. habil. Dieter Vyhnal (HTWK Leipzig)

Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

Embed Size (px)

Citation preview

Page 1: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

HOCHSCHULE FÜR TECHNIK, WIRTSCHAFT UND KULTUR LEIPZIG

Einbindung und Nutzung von 3DConnexion

6dof-Eingabegeräten für auf OpenSG-

basierende Anwendungen in der 3D-

Visualisierung Ein Master-Projekt am Helmholtz-Zentrum für

Umweltforschung GmbH - UFZ

B.Sc. Lars Bilke 28.02.2008

Betreuer:

Dr. Björn Zehner (UFZ)

Prof. Dr. Ing. habil. Dieter Vyhnal (HTWK Leipzig)

Page 2: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

2

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

Page 3: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

3

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

Page 4: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

4

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.

Ziel der am Helmholtz Zentrum für Umweltforschung (UFZ) in Leipzig durchgeführten praktischen Tätigkeiten war es, das SpaceNavigator-Eingabegerät von 3DConnexion (eine Art „3D-Maus“) in das Szenegraphen-System OpenSG3 zur Steuerung der Kameraperspektive und der Positionierung von Objekten im dreidimensionalen Raum einzubinden. Als besondere Anforderung sollte die Eingabe netzwerkgestützt sein, d.h. dass das Eingabegerät nicht unbedingt an dem gleichen Rechner angeschlossen sein muss, auf dem die zu steuernde Anwendung läuft, sondern die Eingabedaten über das Netzwerk verteilt werden können.

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.

1 VRPN – Virtual Reality Peripheral Network

2 6dof – six degrees of freedom, dt.: sechs Freiheitsgrade

3 OpenSG – Open Scenegraph

Abbildung 1 - Eingabegeräte von 3DConnexion, links Space Navigator, rechts Space Pilot

Page 5: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

Im Vorführ-Raum befindet sich der Rechner an dem das Eingabegerät angeschlosseeigentliche Anwendungsrechner kann aber räumlich vom VorführNetzwerk die Eingaben des Benutzers bekommen. Das Rendering erfolgt wiederum ebenfalls räumlich getrennt auf dem RenderVerbindung mit dem Anwendungsrechner.

2. OpenSG

2.1. Überblick

OpenSG ist ein portables SzenengraphenAnwendungen, in erster Linieund kann frei verwendet werden. Es läuft auf zahlreichen Plattformen und BetriebssystemenOpenSG baut auf dem hardwarenahen OpenGL auf, nutzt es zur Grafikausgabe und bildet dabei eine höhere Abstraktionsschicht.Struktur der Szene und kann mit dieser Information diehohen Level optimieren.

4 Ein Szenengraph ist eine objektorientierte Datenstruktur, die häufig in Grafikwird, um die logische und oft auch räumliche Anordnung der darzustellenden zweidreidimensionalen Szene zu beschrieben.

Abbildung 2 - Schematischer Aufbau des Visualisierungszentrums im UFZ

5

Raum befindet sich der Rechner an dem das Eingabegerät angeschlosseeigentliche Anwendungsrechner kann aber räumlich vom Vorführ-Raum getrennt über das Netzwerk die Eingaben des Benutzers bekommen. Das Rendering erfolgt wiederum ebenfalls räumlich getrennt auf dem Render-Cluster. Das Render-Cluster steht über daVerbindung mit dem Anwendungsrechner.

OpenSG ist ein portables Szenengraphen4-System für die Erstellung von EchtzeitAnwendungen, in erster Linie für Virtual Reality. OpenSG wird als OpenSource entwickelt

verwendet werden. Es läuft auf zahlreichen Plattformen und BetriebssystemenOpenSG baut auf dem hardwarenahen OpenGL auf, nutzt es zur Grafikausgabe und bildet dabei eine höhere Abstraktionsschicht. Dabei verfügt der Szenengraph über die komplette Struktur der Szene und kann mit dieser Information die Berechnung der Grafik auf einem

Ein Szenengraph ist eine objektorientierte Datenstruktur, die häufig in Grafik-Anwendungen genutzt wird, um die logische und oft auch räumliche Anordnung der darzustellenden zweidreidimensionalen Szene zu beschrieben.

Schematischer Aufbau des Visualisierungszentrums im UFZ

Raum befindet sich der Rechner an dem das Eingabegerät angeschlossen ist. Der Raum getrennt über das

Netzwerk die Eingaben des Benutzers bekommen. Das Rendering erfolgt wiederum ebenfalls Cluster steht über das Netzwerk in

System für die Erstellung von Echtzeit-Grafik-. OpenSG wird als OpenSource entwickelt

verwendet werden. Es läuft auf zahlreichen Plattformen und Betriebssystemen. OpenSG baut auf dem hardwarenahen OpenGL auf, nutzt es zur Grafikausgabe und bildet

bei verfügt der Szenengraph über die komplette Berechnung der Grafik auf einem

Anwendungen genutzt wird, um die logische und oft auch räumliche Anordnung der darzustellenden zwei- oder

Page 6: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

6

OpenSG ist für keinen bestimmten Anwendungstyp entwickelt worden, sondern eignet sich sowohl für Architektur-Visualisierungen als auch für hochdynamische interaktive virtuelle Welten.

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, …

Page 7: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

7

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

7 ATI-Grafikchip R600, verbaut in der Grafikkarte Radeon HD2900 XT

Page 8: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

8

netzwerkfähiges Format umgewandelt werden. Dazu wird anfangs jedem Datenfeld eine eindeutige ID zugewiesen. Wird nun ein Datenfeld von einem PC geändert, so sendet er ein Netzwerkpaket an die anderen PCs im Cluster mit der Feld-ID und den geänderten Daten als Inhalt. Die anderen PCs setzen diese Änderungen an ihrer Instanz der Anwendung um und arbeiten somit immer auf einem für alle identischen Szenengraphen.

Dies geschieht alles unsichtbar für den Anwendungsprogrammierer. Man muss nur eine spezielle ClusterWindow-Klasse von OpenSG benutzen. OpenSG kümmert sich dann um eine gleichmäßige Auslastung der einzelnen Cluster-PCs und um die abschließende Bildkom-position. [Ope08]

Eine ausführliche Beschreibung Cluster-Implementierung von OpenSG ist unter [Rot05] zu finden.

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.

8 HDR – High Dynamic Range

Page 9: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

9

3. VRPN – Virtual Reality Peripheral Network

VRPN ist eine Klassenbibliothek und eine Sammlung von Server-Anwendungen, die eine geräteunabhängige, netzwerkgestützte Schnittstelle zwischen Anwendungen und einer Anzahl an Eingabegeräten (z.B. Tracker, Buttons, Mäuse, Joysticks) in Virtual Reality-Systemen zur Verfügung stellt.

VRPN wurde entwickelt, um folgenden Problemen gerecht zu werden:

• VR-Systeme, die aus mehreren Rechnern zusammengesetzt sind, z.B. Render-Cluster, benötigen Zugriff auf Eingabegeräte, die physikalisch an verschiedene Rechner an verschiedenen Standorten angeschlossen sind. Dabei wäre es umständlich, die Eingabegeräte immer an die Rechner anschließen zu müssen, an denen die Eingabedaten verarbeitet werden sollen. Außerdem könnte in diesem Fall das Eingabegerät auch nur an einem Rechner benutzt werden.

• Einige VR-Eingabegeräte (vor allem Tracker) arbeiten zuverlässiger wenn sie kontinuierlich angeschaltet sind und benötigen eine längere Startprozedur.

• Verschiedene Eingabegeräte bieten teilweise sehr verschiedene Schnittstellen, obwohl sie über eine ähnliche Funktionalität verfügen. In einigen Fällen gibt es nicht für alle Betriebssysteme auch Treiber für den Betrieb der Eingabegeräte.

• VR-Anwendungen haben die Anforderung einer möglichst niedrigen Latenz zwischen dem Zeitpunkt der Eingabe und dem Feedback durch das VR-System. Außerdem sollen alle Ereignisse (z.B. das Drücken eines Buttons) einem Zeitpunkt zugeordnet werden können und die Zeit soll zwischen Server- und Client-Anwendung synchronisiert werden.

VRPN bietet eine Client-Server-Architektur, mit der es möglich ist, ein Eingabegerät an einen Rechner anzuschließen, auf diesem den VRPN-Server zu starten und an einem anderen über Ethernet verbundenen Rechner auf die Daten des Eingabegerätes über einen VRPN-Client zuzugreifen. [Tay01]

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

Page 10: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

mit zwei Buttons oder eine Maus mit zwei Buttons angeschlossen ist, da sie beide dieselben Typen implementieren. Somit kann die Anwendung mitwerden, 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önnwerden. 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önneSchichten angeordnet sein, d.h. die

So sendet die vrpn_Joystik- Klasse

vrpn_Analog_Fly -Klasse nimmt diese Werte als Eingaben und gibt TrackerVR-Anwendungen durch die 3sie die Analog- oder die Tracker

3.2. VRPN-Verbindungsaufbau

Der Server öffnet einen festgelegten UDPClient öffnet einen verfügbaren TCP10-Port und sendet eine UDPAnfrage an den Server, um eine Verbindung auf diesem TCPaufzubauen. Der Client fragt dann den TCP-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 separatVerbindung 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 Geschwindigkeit gesendet 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 nicht neu gestartet werden, was bei langen Startzeiten der ClientAnwendung sehr vorteilhaft ist.

9 UDP – User Datagram Protocol

10 TCP – Transmission Control Protocol

10

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 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önne

angeordnet sein, d.h. die Ausgabe eines Typs kann Eingabe eines anderen sein.

Klasse z.B. einen analogen Wert für jede JoystickKlasse nimmt diese Werte als Eingaben und gibt Tracker

Anwendungen durch die 3D-Szene zu fliegen. Die Client-Seite kann nun entscheiden, ob oder die Tracker-Daten oder beide benutzt. [Tay01]

Verbindungsaufbau

Der Server öffnet einen festgelegten UDP9-Port für Verbindungsanfragen von Clients. Der Client öffnet einen verfügbaren

Port und sendet eine UDP-um eine

Verbindung auf diesem TCP-Port aufzubauen. Der Client fragt dann

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 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

esendet werden. TCP wäre für Echtzeitanwendungen

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

Anwendung nicht neu gestartet werden, was bei langen Startzeiten der ClientAnwendung sehr vorteilhaft ist. [Tay01]

User Datagram Protocol

Transmission Control Protocol

Abbildung 3 - Anwendungen nutzen ein Einüber verschiedene Klassen

mit zwei Buttons oder eine Maus mit zwei Buttons angeschlossen ist, da sie beide dieselben allen Eingabegeräten genutzt

Obwohl die einzelnen Typen eines Eingabegerätes logisch getrennt sind, werden sie trotzdem

en 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 außerdem in

Ausgabe eines Typs kann Eingabe eines anderen sein.

z.B. einen analogen Wert für jede Joystick-Achse. Die Klasse nimmt diese Werte als Eingaben und gibt Tracker-Daten aus, um in

Seite kann nun entscheiden, ob

Port für Verbindungsanfragen von Clients. Der

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

Anwendung nicht neu gestartet werden, was bei langen Startzeiten der Client-

Anwendungen nutzen ein Eingabegerät

Page 11: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

11

3.3. Vorteile des Client/Server-Aufbaus

Die Aufteilung in 2 Prozesse nach der Client/Server-Architektur von VRPN ist von Vorteil, wenn:

• die Prozesse stark unterschiedliche Update-Raten haben,

• die Server- oder Client-Initialisierung lang dauert,

• die genauen Zeitpunkte der Ereignisse wichtig sind oder

• der Server ständigen Zugriff auf das Eingabegerät benötigt.

So könnte man z.B. die Daten eines PHANTOMs mit 1000 kHz abfragen, um eine Grafik-Anwendung zu steuern, die mit 60 Hz läuft. Würde man das PHANTOM auch nur 60-mal pro Sekunde abfragen, so würde kleine Bewegung wahrscheinlich gar nicht registriert werden oder es könnte z.B. Tastendrücke vollständig verloren gehen.

Im Falle einer langwierigen Initialisierung eines Servers, kann dieser einfach laufen gelassen werden und die Anwendung kann sich dann sofort mit ihm verbinden.

Einige Eingabegeräte benötigen eine ständige Verbindung zum Server oder müssen mit einer bestimmten Frequenz abgefragt werden.

Zum einfachen Testen können ein VRPN-Server und der zugehörige Client auch auf demselben Rechner in zwei separaten Prozessen gestartet werden. Werden der Server und der Client in ein und demselben Prozess gestartet, so werden Ereignisnachrichten direkt an die dafür vorgesehene Callback-Funktion des Clients übermittelt und gehen nicht über das (in diesem Fall nur auf den lokalen Rechner beschränkte) Netzwerk. [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.

Page 12: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

12

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);

Anschließend erstellt man die Klasseninstanz des SpaceNavigators:

if((nav = new vrpn_3DConnexion_Navigator("Gerätenam e@Computername", connection)) == NULL) { printf("Fehler: kann Gerät nicht erstellen\n"); return -1; }

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())

Page 13: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

13

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.

4.3. Eine einfache Client-Anwendung

Nun wird eine Client-Anwendung entwickelt, die sich mit dem eben besprochenem Server verbindet und die vom SpaceNavigator gesendeten Daten auf der Konsole ausgibt.

Wie bereits beschrieben, verbindet sich die Client-Anwendung nicht mit einem SpaceNavigator als solchem, sondern mit abstrahierten Eingabetypen. Im Falle des SpaceNavigators, der über 6 Achsen (Typ Analog mit 6 Werten) und 2 Knöpfe (Typ Button mit 2 Einträgen) verfügt, mit einem Analog- und einem Button-Objekt. Zur Nutzung dieser Objekte bindet man zuerst die entsprechenden Header ein:

#include "vrpn_Button.h" #include "vrpn_Analog.h"

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_BUTTONC HANGEHANDLER)handleButtons);

Die Callback-Funktion hat nun den Namen handleButtons .

Das Gleiche wird nun noch einmal für die vrpn_Analog_Remote -Klasse durchgeführt:

vrpn_Analog_Remote *analog = new vrpn_Analog_Remote ("SpaceNav@viswork01"); analog->register_change_handler(NULL, (vrpn_ANALOGC HANGEHANDLER)handleAnalogs);

Anschließend werden die Hauptschleifen der beiden Eingabeobjekte immer wieder aufgerufen:

while (1) {

Page 14: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

14

button->mainloop(); analog->mainloop(); }

Nun werden die Callback-Funktionen definiert. Da die Button-Callback-Funktion vom Typ vrpn_BUTTONCHANGEHANDLER ist, hat sie folgende Signatur:

void CALLBACK handleButtons(void *, vrpn_BUTTONCB b uttonData)

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", buttonDa ta.button, buttonData.state);

Die Analog-Callback-Funktion hat folgende Signatur:

void CALLBACK handleAnalogs(void *, vrpn_ANALOGCB a nalogData)

In analogData stehen nun Informationen zur Anzahl der Werte (analogData.numChannels ) sowie die Werte selber (analogData.channel[] ) zur Verfügung. Man kann nun in einer Schleife alle Werte auslesen und auf der Konsole ausgeben:

for(int i = 0; i < analogData.num_channel; i++) printf("Channel %d: %f\n", i, analogData.channel[i ]*1000); printf("\n");

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

Page 15: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

15

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 ; static SpaceNavigatorClient * Instance (); void init (char * deviceNamem , SpaceNavigatorAxes axis = Y); void mainloop (); void setDomination (bool dominating ); void switchDomination (); void setMode ( SpaceNavigatorClient :: SpaceNavigatorMode mode); void switchMode (); void setDefaultButtonBehaviour (bool enable ); void invertAxis ( SpaceNavigatorAxes axisToInvert ); 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 ; static void CALLBACK _handleButtons (void *, vrpn_BUTTONCB buttonData ); static void CALLBACK _handleAnalogs (void *, vrpn_ANALOGCB analogData ); };

Page 16: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

16

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.

4.4.2. Implementierung mithilfe des Singleton-Entwurfsmusters

Man kann nur ein SpaceNavigator-Eingabegerät über VRPN ansprechen, wodurch sich die Implementierung der Klasse nach dem Singleton-Entwurfsmuster anbietet. Ein Singleton stellt sicher, dass von einer Klasse nur eine Instanz erzeugt werden kann und stellt einen globalen Zugriff auf diese sicher (siehe auch [Gam95] ).

Um das Entwurfsmuster zu implementieren, muss der Konstruktor als protected deklariert werden und die Erzeugung der Klasse in eine öffentliche, statische Funktion verlagert werden. Ein Zugriff auf die Klasse erfolgt immer über diese Funktion. Die Funktion gibt einen Zeiger auf die Instanz der Klasse zurück. Wurde die Klasse noch nicht instanziiert, so wird der Konstruktor aufgerufen.

SpaceNavigatorClient* SpaceNavigatorClient::Instanc e() { if(_spaceNavigator == 0) _spaceNavigator = new SpaceNavigatorClient(); return _spaceNavigator; }

4.4.3. Initialisierung und Aufbauen einer Verbindung zum VRPN-Server

Mithilfe der init() -Funktion wird die Verbindung zum VRPN-Server aufgebaut. Man übergibt der Funktion als ersten Parameter den Verbindungsstring, der auch im Server angegeben wurde, „Gerätname@Rechnername“, z.B. „SpaceNav@viswork01“. Innerhalb der Funktion werden die Analog- und Button-Objekte wie im vorherigen Beispiel erzeugt und die zugehörigen Callback-Funktionen registriert. Als optionaler Parameter kann die nach oben zeigende Achse auch auf die Z-Achse gesetzt werden. Wird dieser Parameter nicht angegeben, so zeigt die Y-Achse nach oben.

void init(char *deviceName, SpaceNavigatorAxes axis ) { button = new vrpn_Button_Remote(deviceName); button->register_change_handler(NULL, (vrpn_BUTTONCHANGEHANDLER)SpaceNavigatorClient::h andleButtons); analog = new vrpn_Analog_Remote(deviceName); analog->register_change_handler(NULL, (vrpn_ANALOGCHANGEHANDLER)SpaceNavigatorClient::h andleAnalogs); if(axis == Z) upAxis = axis; else upAxis = Y; }

Page 17: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

4.4.4. Bereitstellen des aktuellen Zustands des SpaceNavigators

Der aktuelle Zustand des SpaceNavigators wird in folgenden öffentlichen Variablgespeichert:

bool buttons[2]; float x, y, z; float rx, ry, rz;

Die Werte werden in den CallbackButton-Callback-Funktion:

void CALLBACK handleButtons(void *, vrpn_BUTTONCB b uttonData){ spacenavigator- >buttons[buttonData.button] = (bool)buttonData.stat e; … }

In der Analog-Callback-Funktion werden ebenso die einzelnen Werte in den öffentlichen Variablen gespeichert:

void CALLBACK handleAnalogs(void *, vrpn_ANALOGCB a nalogData){

spacenavigator- >x = analogData.channel[0] * spacenavigator- >y = analogData.channel[2] * spacenavigatorspacenavigator- >z = analogData.channel[1] * spacenavigatorspacenavigator- >rx = analogData.channel[3] * spacenavigatorspacenavigator- >ry = analogData.channel[5] * spacenavigatorspacenavigator- >rz = analogData.channel[4] * spacenavigator

}

Abbildung 4 - Die Verarbeitung der Space Navigator

SpaceNavigatorClient::

normal invertiert

x

vrpn_ANALOGCB

channel[0] channel

Bewegung des Space Navigators

17

Bereitstellen des aktuellen Zustands des SpaceNavigators

Der aktuelle Zustand des SpaceNavigators wird in folgenden öffentlichen Variabl

Die Werte werden in den Callback-Funktionen aktualisiert. Folgendes Listing zeigt die

void CALLBACK handleButtons(void *, vrpn_BUTTONCB b uttonData)

>buttons[buttonData.button] = (bool)buttonData.stat e;

Funktion werden ebenso die einzelnen Werte in den öffentlichen

void CALLBACK handleAnalogs(void *, vrpn_ANALOGCB a nalogData)

>x = analogData.channel[0] * spacenavigator- >invertAxes[0];>y = analogData.channel[2] * spacenavigator - >invertAxes[1];>z = analogData.channel[1] * spacenavigator - >invertAxes[2];>rx = analogData.channel[3] * spacenavigator - >invertAxes[3];>ry = analogData.channel[5] * spacenavigator - >invertAxes[4];>rz = analogData.channel[4] * spacenavigator - >invertAxes[5];

Die Verarbeitung der Space Navigator-Eingaben

SpaceNavigatorClient::invertAxes[]

invertiert normal normal invertiert normal

SpaceNavigatorClient

y z rx ry rz

vrpn_ANALOGCB analogData

channel[2] channel[1] channel[3] channel[5] channel[4]

Bewegung des Space Navigators

Bereitstellen des aktuellen Zustands des SpaceNavigators

Der aktuelle Zustand des SpaceNavigators wird in folgenden öffentlichen Variablen

Funktionen aktualisiert. Folgendes Listing zeigt die

>buttons[buttonData.button] = (bool)buttonData.stat e;

Funktion werden ebenso die einzelnen Werte in den öffentlichen

>invertAxes[0]; >invertAxes[1]; >invertAxes[2];

>invertAxes[3]; >invertAxes[4]; >invertAxes[5];

Eingaben

Page 18: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

18

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.

4.4.5. Verschiedene Funktionsmodi

Es gibt 3 verschiedene Funktionsmodi:

• Translation und Rotation

• Nur Translation

• Nur Rotation

Der erste Modus entspricht dem Standardverhalten, wie es bereits im vorigen Abschnitt in der Callback-Funktion implementiert wurde.

Der Translations-Modus entspricht ebenfalls dem ersten Modus, nur dass die Rotationswerte nicht ausgelesen werden sondern diese auf 0 gesetzt werden:

spacenavigator->x = analogData.channel[0] * spacena vigator->invertAxes[0]; spacenavigator->y = analogData.channel[2] * spacena vigator->invertAxes[1]; spacenavigator->z = analogData.channel[1] * spacena vigator->invertAxes[2]; spacenavigator->rx = spacenavigator->ry = spacenavi gator->rz = 0;

Der Rotationsmodus verhält sich vergleichbar:

spacenavigator->rx = analogData.channel[3] * spacen avigator->invertAxes[3]; spacenavigator->ry = analogData.channel[5] * spacen avigator->invertAxes[4]; spacenavigator->rz = analogData.channel[4] * spacen avigator->invertAxes[5]; spacenavigator->x = spacenavigator->y = spacenaviga tor->z = 0;

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 = spacenavig ator->z = 0; spacenavigator->rx = spacenavigator->ry = spacena vigator->rz = 0; switch(index) { case 0: spacenavigator->x = max * spacenavigator- >invertAxes[0]; break;

Page 19: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

19

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; } }

Die Implementierung für die anderen beiden Modi ist ähnlich.

4.4.7. Invertierung der Achsen

In der nichtöffentlichen Variable invertAxes werden die zu invertierenden Achsen gespeichert. Standardmäßig werden die Y-Achse und die Rotation um die Y-Achse invertiert, um ein Verhalten der SpaceNavigator-Steuerung analog zu der im SpaceNavigator-Handbuch zu erreichen. Dies bleibt dem Benutzer der Klasse aber verborgen. Er benutzt nur die Funktion invertAxis(SpaceNavigatorAxis axisToInvert) und gibt dabei als Parameter die zu invertierende Achse ein. Er muss sich nicht darum kümmern, ob eine Achse bereits invertiert wurde, was der folgende Quellcode zeigt:

void SpaceNavigatorClient::invertAxis(SpaceNavigato rAxes axisToInvert) { if(axisToInvert < 7 && axisToInvert > 0) { if(invertAxes[axisToInvert - 1] == 1.f) invertAxes[axisToInvert - 1] = -1.f; else invertAxes[axisToInvert -1] = 1.f; } }

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(); }

Der komplette Quellcode der SpaceNavigator-Klasse ist im Projektordner SpaceNavigator/ SpaceNavigatorClient zu finden.

4.4.9. Die Klasse benutzen

Um die Klasse zu benutzen muss im Quellcode die zugehörige Headerdatei eingebunden werden:

#include "../SpaceNavigator/SpaceNavigatorClient.h"

Page 20: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

20

Außerdem benötigt man zum Zugriff auf die Objektinstanz der Klasse einen Zeiger:

SpaceNavigatorClient* spaceNavigator;

Die statische Funktion SpaceNavigatorClient::Instance() gibt einen Zeiger auf die SpaceNavigator-Klasse zurück und die Initialisierungs-Funktion muss aufgerufen werden:

spaceNavigator = SpaceNavigatorClient::Instance(); spaceNavigator->init("SpaceNav@viswork01");

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::Instan ce()->x;

4.4.10. Beispielanwendung: Bewegen und Drehen eines Würfels in OpenSG

In diesem Abschnitt wird eine einfache OpenSG-Anwendung entwickelt, in der man einen Würfel mithilfe des SpaceNavigators verschieben und rotieren kann. Es wird der SimpleSceneManager, eine Hilfsklasse aus OpenSG, genutzt, um die Anwendung mit wenig Code zu erstellen. Der SimpleSceneManager kümmert sich um Dinge wie das Erzeugen und Positionieren der Kamera, das Erzeugen des Viewports, die Behandlung von Tastendrücken und Mauseingaben.

Am Anfang des Quellcodes werden alle benötigten Header eingebunden, dazu zählt auch der Header der erstellten SpaceNavigator-Klasse:

#include <OpenSG/OSGConfig.h> #include <OpenSG/OSGGLUT.h> #include <OpenSG/OSGSimpleGeometry.h> #include <OpenSG/OSGGLUTWindow.h> #include <OpenSG/OSGSimpleSceneManager.h> #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.

Page 21: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

SpaceNavigatorClient* spaceNavigator;SimpleSceneManager* mgr; TransformPtr tMain;

Die Setup-Funktion von GLUT muss vor den anderen Funktionen des Programms deklariert werden.

int setupGLUT(int *argc, char *argv[]);

Anschließend wird in der createScene()

Würfel mitsamt zugehöriger Transformation erstellt. Die Funktion gibt einen NodePtr

Transformation sowie der Würfel angehängt sind.

Zuerst wird eine Matrix-Variable angelegt:

NodePtr createScene(void) { Matrix m;

Anschließend wird die BoxOpenSG-Funktion makeBox() erzeugt.

NodePtr box = makeBox(20, 20, 20, 1, 1, 1);

Für die globale TransformPtr

Transformationsmatrix als Identitätsmatrix initialisiert:

tMain = Transform::create(); beginEditCP(tMain, Transform::MatrixFieldMask); m.setIdentity(); tMain->setMatrix(m); endEditCP(tMain, Transform::MatrixFieldMask);

Es wird ein weiterer Knoten erzeugtdessen Core auf das globale Transform

NodePtr mainTrans = Node::create(); beginEditCP(mainTrans, Node::CoreFieldMask | Node:: ChildrenFieldMask); mainTrans- >setCore(tMain); mainTrans- >addChild(box); end EditCP(mainTrans, Node::CoreFieldM

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::Children FieldMa n- >setCore(Group::create()); n- >addChild(mainTrans); endEditCP(n, Node::CoreFieldM

Dieser Wurzelknoten wird von der Funktion zurückgegeben:

return n; }

Damit ist der Szenengraph (siehe kümmert sich der SimpleSceneManager um die Erstellung und Positionierung einer Kamera.

In der main() -Funktion wird als erstes das SpaceNavigatorZur Initialisierung muss der Name des SpaceNavigators auf dem Server und der Rechnername des Servers als

21

SpaceNavigatorClient* spaceNavigator;

Funktion von GLUT muss vor den anderen Funktionen des Programms deklariert werden.

int setupGLUT(int *argc, char *argv[]);

createScene() -Funktion der Würfel mitsamt zugehöriger Transformation erstellt.

NodePtr zurück, an den die Transformation sowie der Würfel angehängt sind.

Variable angelegt:

Anschließend wird die Box-Geometrie mit der erzeugt.

NodePtr box = makeBox(20, 20, 20, 1, 1, 1);

TransformPtr -Variable wird ein Transform-Objekt erzeugt und dessen als Identitätsmatrix initialisiert:

tMain = Transform::create(); beginEditCP(tMain, Transform::MatrixFieldMask);

endEditCP(tMain, Transform::MatrixFieldMask);

Es wird ein weiterer Knoten erzeugt, an den der Knoten mit der Box angehangen wird und dessen Core auf das globale Transform-Objekt zeigt.

NodePtr mainTrans = Node::create(); beginEditCP(mainTrans, Node::CoreFieldMask | Node:: ChildrenFieldMask);

>setCore(tMain); >addChild(box);

EditCP(mainTrans, Node::CoreFieldM ask | Node::ChildrenFieldMask);

Nun erzeugt man den Wurzelknoten des Szenengraphen und hängt den zuletzt erzeugten

NodePtr n = Node::create(); beginEditCP(n, Node::CoreFieldMask | Node::Children FieldMa sk);

>setCore(Group::create()); >addChild(mainTrans);

endEditCP(n, Node::CoreFieldM ask | Node::ChildrenFieldMask);

Dieser Wurzelknoten wird von der Funktion zurückgegeben:

(siehe Abbildung 5) vollständig erstellt. Wie bereits erwähnt, kümmert sich der SimpleSceneManager um die Erstellung und Positionierung einer Kamera.

Funktion wird als erstes das SpaceNavigator-Objekt erstellt und initialisiert. der Name des SpaceNavigators auf dem Server und der Kommandozeilen-Parameter übergeben werden

Abbildung 5 - Der Aufbau der Szene

Geometry makeBox

Transform tMain

Matrix m

Group::create()

Objekt erzeugt und dessen

en mit der Box angehangen wird und

beginEditCP(mainTrans, Node::CoreFieldMask | Node:: ChildrenFieldMask);

ask | Node::ChildrenFieldMask);

Nun erzeugt man den Wurzelknoten des Szenengraphen und hängt den zuletzt erzeugten

vollständig erstellt. Wie bereits erwähnt, kümmert sich der SimpleSceneManager um die Erstellung und Positionierung einer Kamera.

Objekt erstellt und initialisiert. der Name des SpaceNavigators auf dem Server und der

Parameter übergeben werden:

Der Aufbau der Szene

Node n

Node mainTrans

Node box

Geometry makeBox

Transform tMain

Group::create()

Page 22: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

22

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); int winid = setupGLUT(&argc, argv); GLUTWindowPtr gwin = GLUTWindow::create(); gwin->setId(winid); gwin->init();

Die createScene() -Funktion wird aufgerufen.

NodePtr scene = createScene();

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;

Page 23: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

23

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, sp aceNavigator->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 * spac eNavigator->ry ) * rotFactor)); rot.mult(Quaternion(Vec3f(1,0,0), (3.14159 * spac eNavigator->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);

Die Transformationsmatrizen werden miteinander multipliziert was ebenso einer Verkettung der Transformationen entspricht. Die Matrizen müssen folgendermaßen multipliziert werden: alte Translation * Translation * Rotation.

old.mult(t); old.mult(r);

Schließlich wird die Transformationsmatrix des Transform-Objekts der Box auf die finale Transformationsmatrix gesetzt:

beginEditCP(tMain); tMain->setMatrix(old); endEditCP(tMain);

Natürlich wird die Szene mithilfe des SimpleSceneManagers gerendert.

mgr->redraw(); }

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_D OUBLE);

Mit glutCreateWindow() wird das eigentliche Programmfenster erzeugt. Als Parameter wird der Fenstername übergeben.

Page 24: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

24

int winid = glutCreateWindow("OpenSG First App");

Abschließend wird die Display-Funktion als GLUT-Callback registriert und die GLUT-Idle-CALLBACK-Funktion ebenfalls auf die Display-Funktion gesetzt.

glutDisplayFunc(display); glutIdleFunc(display); return winid; }

In dem Programm kann man nun den Würfel mithilfe des SpaceNavigators bewegen und rotieren. Die Transformation erfolgt dabei immer relativ zum ursprünglichen OpenSG-Koordinatensystem.

Das Programm muss über die Kommandozeile in folgender Weise gestartet werden:

SpaceNavigatorTestObjectMode.exe SpaceNav@Rechnerna me

Der vollständige Quellcode ist im Projektordner SpaceNavigator/SpaceNavigator-TestObjectMode zu finden.

4.5. Der SpaceNavigatorSSM

Um die Funktionalität der SpaceNavigator-Klasse schnell und einfach in neuen Anwendungen nutzen zu können, wird der in OpenSG enthaltene SimpleSceneManager abgeleitet und mit neuer Funktionalität erweitert, der SpaceNavigatorSSM. Man kann ihn anstatt des normalen Scene Managers verwenden.

Der SpaceNavigatorSSM soll folgende Funktionen implementieren:

1. Abgeleitet von SimpleSceneManager � bereits bestehende Anwendungen, die den SimpleSceneManager nutzen, können ohne großen Aufwand mit der neuen Funktionalität versehen werden.

2. Auswählen der Hoch-Achse: wahlweise Y- (Standard) oder Z-Achse 3. Kamerasteuerung mit fester Höhe über Grund (frameratenunabhängig) 4. Auswählen von Objekten per Mausklick 5. Ausgewählte Objekte können mit dem SpaceNavigator bewegt und rotiert werden (die

Kamera ist in diesem Moment fest) 6. Standardmäßig wird der WalkNavigator benutzt, um sich auf einer spezifizierten

Oberfläche bewegen zu können (die Funktionalität des TrackballNavigators bleibt aber weiterhin erhalten, man kann zwischen beiden umschalten)

7. Performanceoptimierte Bewegung auf einer Oberfläche mit der Klasse Triangle-ElevationGrid

4.5.1. Ableiten von SimpleSceneManager

Der SpaceNavigatorSSM ist vom OpenSG SimpleSceneManager öffentlich abgeleitet, d.h. er erbt alle Funktionen und Variablen.

Abbildung 6 - Die Beispielanwendung zum Drehen eines Würfels

Page 25: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

25

class SpaceNavigatorSSM : public osg::SimpleSceneMa nager

Es werden hauptsächlich neue Funktionen hinzugefügt, aber auch Funktionen überschrieben wie setRoot() , showAll() , mouseButtonPress() und initialize() .

Neue Variablen werden als private Felder deklariert und sind nur durch Get- und Set-Methoden zugänglich. Die Headerdatei des SpaceNavigatorSSM verdeutlicht den Aufbau der Klasse:

#pragma once #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/TriangleElevatio nGrid.h" OSG_USING_NAMESPACE class SpaceNavigatorSSM : public SimpleSceneManager { public: SpaceNavigatorSSM(char *deviceName, bool zUpAxis = false); void setRoot(osg::NodePtr root); void showAll(void); SpaceNavigatorClient* getSpaceNavigator(); void updateCameraMovement(); void setDefaultButtonBehaviour(bool enable); void setHeightControl(bool enable); void switchHeightControl(); void setObjectPicking(bool enable); void switchObjectPicking(); void setTranslationFactor(float factor); void setRotationFactor(float factor); void setCameraPosition(osg::Pnt3f position); void mouseButtonPress(osg::UInt16 button, osg::Int 16 x, osg::Int16 y); void initWalkNavGroundCollision(NodePtr groundNode ); void initElevationGrid(char *gridFile, int numXCel ls = 10, int numYCells = 10); void setGroundDistance(float distance); NodePtr getNodeByName(NodePtr rootNode, const Char 8 *nodeName); 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; };

Page 26: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

26

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 *deviceNa me, 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; }

4.5.2. Auswählen der Hoch-Achse

Die Hoch-Achse wurde bereits im Konstruktor gesetzt. Das Koordinatensystem ist ein links-händiges Koordinatensystem, wenn die Y-Achse die Hochachse ist. Ist die Z-Achse die Hochachse, so handelt es sich um ein rechtshändiges Koordinatensystem (siehe Abbildung 7).

Page 27: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

27

Die setRoot() -Methode, die vom SimpleSceneManager geerbt wird, wird überschrieben, um die Hoch-Achse in der dem SimpleSceneManager zugehörigen Navigator-Klasse zu setzen.

void SpaceNavigatorSSM::setRoot(osg::NodePtr root) { this->initialize(); 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->g etFov() / 2.f)); else dist = osgMax(d[0],d[1]) / (2 * osgtan(_camera->g etFov() / 2.f)); Vec3f up = getNavigator()->getUp(); Pnt3f at;

Abbildung 7 – links: OpenSG-Koordinatensystem (Y-Achse ist Hochachse), rechts: rechtshän-diges Koordinatensystem mit Z-Achse als Hochachse

Page 28: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

28

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.

4.5.3. Kamerasteuerung mit fester Höhe über Grund

Herzstück der Klasse ist die updateCameraAndMovement() -Methode, die die Kamerasteuerung implementiert. Wenn diese Klasse in einem Programm benutzt wird, muss diese Methode einmal pro Frame aufgerufen werden. Um die Kamerabewegung frameratenunabhängig zu machen, wird die seit dem letzten Frame vergangene Zeit benötigt und mit deren Hilfe skaliert. Das bedeutet, dass die Kamerabewegung immer mit der gleichen Geschwindigkeit abläuft, egal, ob die Anwendung mit 20 oder mit 60 Bildern pro Sekunde läuft. Würde man dies nicht berücksichtigen, so würde sich die Geschwindigkeit der Kamerabewegungen z.B. mit steigender Bildrate erhöhen.

Die vergangene Zeit wird mithilfe der GLUT-Methode glutGet(GLUT_ELAPSED_TIME) und der beim letzten Frame gespeicherten Zeit berechnet.

void SpaceNavigatorSSM::updateCameraMovement() { int newElapsedTime = glutGet(GLUT_ELAPSED_TIME); int frameTime = (newElapsedTime - _elapsedTime); / / in ms _elapsedTime = glutGet(GLUT_ELAPSED_TIME);

Anhand der berechneten Zeit und der gespeicherten Faktoren wird die Empfindlichkeit der Translation und Rotation des SpaceNavigators angepasst.

float transFactor = _translationFactor * (frameTi me / 1000.0); float rotFactor = _rotationFactor * (frameTime / 8 00.0); float rotFactorObject = rotFactor * 3.0f;

Die SpaceNavigator-Hauptschleife muss natürlich auch einmal pro Frame aufgerufen werden:

SpaceNavigatorClient* spaceNavigator = SpaceNaviga torClient::Instance(); spaceNavigator->mainloop();

Ein Zeiger auf den WalkNavigator der Basisklasse wird unter dem Namen wnav gespeichert:

WalkNavigator* wnav = _navigator.getWalkNavigator( );

Anschließend wird zwischen dem WALK- und dem TRACKBALL-Modus unterschieden, wobei im Falle des TRACKBALL-Modus nix weiter getan wird, denn diese Funktionalität ist bereits in der Basisklasse implementiert.

Page 29: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

29

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;

Zur Kamerabewegung wird auf Funktionalität des WalkNavigators zurückgegriffen. So kann man mit rotate() die Kamera um 2 Achsen rotieren, mit forward() die Kamera vor und zurück bewegen und mit right() die Kamera nach rechts und links bewegen. Der anfangs berechnete Translations- oder Rotationsfaktor wird mit dem entsprechenden Wert des SpaceNavigators multipliziert und das Ergebnis an eine dieser Funktionen übergeben. Entsprechend der eingestellten Hoch-Achse werden die richtigen SpaceNavigator-Achsen in den Funktionen verwendet:

if(_zUpAxis) { wnav->rotate(spaceNavigator->rz*-rotFactor,spa ceNavigator->rx*-rotFactor); wnav->forward(spaceNavigator->y * transFactor) ; } else { wnav->rotate(spaceNavigator->ry*-rotFactor,spa ceNavigator->rx*-rotFactor); wnav->forward(_spaceNavigator->z * transFactor ); } wnav->right(_spaceNavigator->x * -transFactor);

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 * transFa ctor; else _groundDistance += spaceNavigator->y * transFa ctor; if(_groundDistance < 0.1f) _groundDistance = 0. 1f; wnav->setGroundDistance(_groundDistance); } }

Page 30: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

30

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 but ton, 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->ge tRoot()) _pickedObjectNode = _pickedObjectNode->getPa rent();

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::creat e(); Matrix m; m.setIdentity(); beginEditCP(newTransform, Transform::MatrixF ieldMask); newTransform->setMatrix(m); endEditCP(newTransform, Transform::MatrixFie ldMask);

Page 31: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

31

NodePtr newTransformNode = Node::create(); beginEditCP(newTransformNode, Node::CoreFiel dMask); newTransformNode->setCore(newTransform); endEditCP(newTransformNode, Node::CoreFieldM ask); NodePtr pickedObjectParent = pickedObject->g etParent(); addRefCP(pickedObject); beginEditCP(pickedObjectParent); pickedObjectParent->replaceChildBy(pickedOb ject,

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); } }

Abschließend wird der Maustastendruck an den Navigator der Basisklasse weitergeleitet, um auch den TrackballNavigator nutzen zu können.

_navigator.buttonPress(Navigator::LEFT_MOUSE,x,y ); break;

Dies geschieht ebenso für die anderen Maustastendrücke:

case MouseMiddle: _navigator.buttonPress(Navigator::MIDDLE_MOUSE,x ,y); break; 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;

Page 32: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

32

this->setHighlight(NullFC); _navigator.buttonPress(Navigator::RIGHT_MOUSE,x, y); break; }

Die restlichen Codezeilen entsprechen denen in der überschriebenen Methode:

_mousebuttons |= 1 << button; _lastx = x; _lasty = y; }

4.5.5. Ausgewählte Objekte rotieren und verschieben

Das Rotieren und Verschieben von Objekten geschieht in der updateCameraAndMovement() -Methode. Es wurden jedoch deutliche Änderungen im Gegensatz zum Code aus der Beispielanwendung mit dem Würfel gemacht. Die Verschiebung geschieht immer relativ zur Orientierung der Kamera. Verschiebt man also ein Objekt „nach hinten“, so bewegt es sich auch wirklich von einem weg und nicht entlang einer festen Achse des Koordinatensystems. Dies ist deutlich intuitiver. Außerdem sollte es ebenso möglich sein, dass Objekte transformiert werden können die im Graphen unter mehreren Transformationen angeordnet sind. Die Transformation wird relativ zur Kamera ausgeführt und in eine Transformation relative zum ersten Transformationsknoten umgerechnet. Die übergeordneten Transformationen bleiben also unangetastet. Zuerst werden einige temporäre Variablen angelegt.

if(_pickedObjectNode != NullFC) { Vec3f dummy1, dummy2; Quaternion rotation, dummy3;

Anschließend wird die Rotation der Kamera in Weltkoordinaten in der Matrix camToWorld gespeichert.

Matrix camToWorld = getCamera()->getBeacon()->getTo World(); 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->getToW orld(); objectToWorld.getTransform(dummy1, rotation, dum my2, dummy3); objectToWorld.setIdentity(); objectToWorld.setRotate(rotation); objectToWorld.transpose();

Der Verschiebungsvektor dv wird mithilfe der Eingaben des SpaceNavigators in Abhängigkeit zur Hoch-Achse erstellt. In den beiden Variablen dvworld und dvobject wird der Vektor später in andere Koordinatensystemen umgewandelt.

Vec3f dv; if(_zUpAxis) dv.setValues(spaceNavigator->x*transFactor,spac eNavigator->z*transFactor,

spaceNavigator->y * transFactor); else dv.setValues(spaceNavigator->x*transFactor,spac eNavigator->y*transFactor,

spaceNavigator->z * transFactor); Vec3f dvworld;

Page 33: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

33

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);

Der Verschiebungsvektor wird in Objektkoordinaten umgerechnet und die drei Kameraachsen werden in cameraREx erstellt.

camToObject.mult(dv, dvobject); Vec3f cameraRE1(1, 0, 0); Vec3f cameraRE2(0, 1, 0); Vec3f cameraRE3(0, 0, 1);

Die Kameraachsen werden nun in Objektkoordinaten transformiert und das Ergebnis in objectREx gespeichert.

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 * rotFa ctorObject); 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(_pickedObje ctNode->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();

Page 34: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

34

m.setTranslate(dvobject); transform.mult(m); transform.setRotate(rotation);

Anschließend wird im Falle eines normalen Transformations-Knoten die Transformations-Matrix gesetzt. Im Falle einer ComponentTransform müssen die Translation und die Rotation einzeln gesetzt werden.

if(_pickedObjectNode->getCore()->getType().isDerive dFrom (ComponentTransform::getClassType()))

{ // set translation and rotation separately ComponentTransformPtr compTrans = ComponentTran sformPtr::dcast

(_pickedObjectNode->getCore()); beginEditCP(compTrans); compTrans->setTranslation(Vec3f(transform[3][0 ],transform[3][1],

transform[3][2])); compTrans->setRotation(rotation); endEditCP(compTrans); } else if(_pickedObjectNode->getCore()->getType(). isDerivedFrom

(Transform::getClassType())) { // set final matrix TransformPtr transCore = TransformPtr::dcast(_p ickedObjectNode

->getCore()); beginEditCP(transCore, Transform::MatrixFieldMa sk); transCore->setMatrix(transform); endEditCP(transCore, Transform::MatrixFieldMask ); }

4.5.6. Auf der Oberfläche bewegen

Ist der Walk-Modus aktiv, kann man sich in einem festgelegten Abstand über die Oberfläche eines Objektes bewegen. Dies ist bereits im WalkNavigator der Basisklasse implementiert. Man aktiviert diesen Modus mit dem Aufruf der Funktion SpaceNavigatorSSM::

initWalkNavGroundCollision() . Man übergibt der Funktion einen Szenenknoten, der den Boden enthält. Die Höhe über Grund wird über SpaceNavigatorSSM::setGroundDistance() geregelt.

Mit dem Aufruf setHeightControl(true) aktiviert man die Steuerung der Höhe über den SpaceNavigator. Hebt man den SpaceNavigator, so steigt die Höhe und drückt man den SpaceNavigator nach unten so sinkt die Höhe.

4.5.7. Optimierte Bewegung auf der Oberfläche

Aufgrund der sehr einfachen Implementierung der Bodenkollisionsabfrage im WalkNavigator, ist dieses Verfahren für komplexere Bodengeometrien nicht praktikabel. Deswegen wurde ein intelligenteres Verfahren über die Klasse CTriangleElevationGrid (geschrieben von Björn Zehner) implementiert. Dabei wird die Geometrie in ein regelmäßiges Gitter aufgeteilt. Man aktiviert diese Art der Kollisionserkennung mit dem Boden mit dem Aufruf der Methode SpaceNavigatorSSM::initElevationGrid() . Der Methode wird ein Dateiname übergeben, in der die Bodengeometrie enthalten ist. Außerdem gibt man an, in wie viele Zellen die Bodengeometrie aufgeteilt wird. Je mehr Zellen desto performanter ist die Kollisionserkennung aber desto mehr Speicher wird auch benötigt. Geeignete Werte für

Page 35: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

35

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.

4.5.8. Weitere Funktionen

Mithilfe der Methode setCameraPosition() kann man den Standpunkt der Kamera auch manuell festlegen. Die Orientierung der Kamera wird dabei beibehalten.

void SpaceNavigatorSSM::setCameraPosition(osg::Pnt3 f position) { Navigator* nav = getNavigator(); nav->setFrom(position); nav->setAt(nav->getAt() + position - nav->getFrom( )); }

Ü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.

4.5.9. Beispielanwendung: Bewegung auf einem Stadtmodell

Zuerst wird die Headerdatei der Klasse eingebunden.

#include "../SpaceNavigatorSSM/SpaceNavigatorSSM.h"

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); int winid = setupGLUT(&argc, argv); GLUTWindowPtr gwin= GLUTWindow::create(); gwin->setId(winid); gwin->init();

Die Szene, bestehend aus einem virtuellen Straßenabschnitt, wird aus einer Datei gelesen.

NodePtr scene = SceneFileHandler::the().read("../os g/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 );

Page 36: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

36

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);

Die GLUT-Anwendungsschleife wird betreten.

glutMainLoop(); 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.

4.5.10. Beispielanwendung: Bewegung auf einem Landschaftsmodell

Dieses Beispiel ist analog zum vorherigen mit dem Unterschied, dass ein hügeliges, aus über 50000 Dreiecken bestehendes Landschaftsmodell lädt. Im Folgenden werden nur Unterschiede im Code zum vorherigen Beispiel erklärt. Die Szene wird geladen.

Scene = SceneFileHandler::the().read("../osg/grou nd_example_2000.osb");

Das Modell wurde mit der Z-Achse als Hoch-Achse modelliert. Deswegen wird als zweiter Parameter bei der Erzeugung des SceneManagers true übergeben.

Abbildung 8 - Ein Objekt wird mithilfe der Maus ausgewählt und mit dem SpaceNavigator gedreht und angehoben

Page 37: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

37

mgr = new SpaceNavigatorSSM(computerName, true);

Anstatt der Standard-Kollisionserkennung des WalkNavigators wird die optimierte Kollisionserkennung der Klasse CTriangleElevationGrid benutzt. Dabei wird das Landschaftsmodell in 15x15 gleichgroße Teile unterteilt. Zur Kollisionserkennung wird immer nur der Teil getestet, in dem sich die Kamera befindet.

mgr->initElevationGrid("../osg/ground_example_200 0.osb", 15, 15);

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));

4.6. Installation und Testen im VR-Pool der HTWK Leipzig Im VR-Pool der HTWK ist die Verbindung der Anwendung zum Eingabegerät über das Netzwerk ebenfalls sehr nützlich. So kann die über den SpaceNavigator zu steuernde Anwendung auf dem Rechner laufen, der das Stereo-Display ansteuert. Dieser ist hinter dem Display positioniert. Der SpaceNavigator kann dann an einen Rechner der vor dem Display steht angeschlossen werden und somit die Anwendung bequem gesteuert werden.

4.6.1. Den SpaceNavigator-Server einrichten

Der SpaceNavigator-Server muss auf dem Rechner installiert und gestartet werden, an den der SpaceNavigator angeschlossen wird. Dazu kopiert man das Verzeichnis Server von der Abgabe-CD in ein Verzeichnis auf Festplatte.

Man hat die Möglichkeit den allgemeinen VRPN-Server benutzen. Dazu startet man die Datei vrpn_server.exe. Anschließend kann man bereits die Anwendung auf dem zweiten Rechner starten. Sind die Bewegungen in der Anwendung ruckelig, so empfiehlt sich stattdessen der Einsatz der SpaceNavigatorServer.exe, da diese ohne Pause die Eingaben des SpaceNavigators verarbeitet während der VRPN-Server dies nur alle 5ms tut. Die SpaceNavigatorServer.exe wird über die entsprechende .bat-Datei gestartet. Dies ist nötig, weil die der Server dem Eingabegerät einen eindeutigen String zuweisen muss, der aus einem Namen und dem Rechnernamen des Server-PCs bestehen muss. Die .bat-Datei übergibt der Anwendung den Verbindungsstring wie folgt:

SpaceNavigatorServer.exe SpaceNav@computername

Wird der SpaceNavigator an den Rechner mit dem EOS-Tracking-System angeschlossen, so müsste die .bat-Datei folgendermaßen aussehen:

SpaceNavigatorServer.exe SpaceNav@eos-ir-tracking

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.

Page 38: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

38

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.

4.7. VRPN erweitern

Da im VR-Pool und auch im UFZ der SpacePilot von 3DConnexion zum Einsatz kommt wurde der Standard-Server von VRPN erweitert. Auf die Implementierung im Speziellen wird hier nicht weiter eingegangen. Die geänderten VRPN-Quellcode-Dateien liegen im Verzeichnis SpaceNavigator/vrpn. Die hinzugefügten Zeilen sind jeweils mit dem Kommentar // Changed: 22.01.08, Bilke gekennzeichnet.

Der kompilierte Server liegt im Release-Verzeichnis des Projektes. Die zugehörige Konfigurationsdatei sieht nun folgendermaßen aus:

vrpn_3DConnexion_Navigator SpaceNav #vrpn_3DConnexion_Traveller SpaceTrav #vrpn_3DConnexion_Pilot SpacePilot #vrpn_3DConnexion_Explorer SpaceExp

Je nachdem, welches Eingabegerät man verwendet, so entfernt man das Kommentarzeichen # in der jeweiligen Zeile. Im obigen Falle wird der SpaceNavigator verwendet.

Die Dateien im Server- und Client-Ordner sind bereits auf die Verwendung des SpacePilot angepasst.

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.

5.2. Ordner download

Dieser Ordner enthält die 3DConnexion-Treiber (3DxSoftware_v3-5-5.exe), den OpenSG-Quellcode (opensg_dailybuild.070704.win-source.tgz) und den VRPN-Quellcode (vrpn_07_13.zip).

5.3. Ordner Server

Dieser Ordner enthält die Dateien, die im VR-Pool auf den EOS-Tracking-Rechner kopiert werden müssen.

Page 39: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

39

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.

5.5. Ordner opensg

Dieser Ordner enthält die kompilierte OpenSG-Bibliothek sowie den zugehörigen Quellcode.

5.6. Ordner vrpn

Dieser Ordner enthält die kompilierte VRPN-Bibliothek sowie den zugehörigen Quellcode.

Page 40: Einbindung und Nutzung von 3DConnexion 6dof-Eingabegeräten

40

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/.