INSTITUT FÜR INFORMATIK Softwaretechnik und Programmiersprachen Universitätsstr. 1 D–40225 Düsseldorf 3-D Visualisierung von Zustandsräumen formaler Modelle Marcus Ihde-Meister Bachelorarbeit Beginn der Arbeit: 04. April 2007 Abgabe der Arbeit: 04. Juli 2007 Gutachter: Prof. Dr. Michael Leuschel Prof. Dr. Michael Schöttner 2 3 Erklärung Hiermit versichere ich, dass ich diese Bachelorarbeit selbstständig verfasst habe. Ich habe dazu keine anderen als die angegebenen Quellen und Hilfsmittel verwendet. Düsseldorf, den 03. Juli 2007 Marcus Ihde-Meister 4 Zusammenfassung Formale Modelle beschreiben die Funktionalität eines Objekts oder eines Verfahrens mittels einer eindeutig definierten formalen Sprache. Die Menge aller Zustände eines formalen Modells bilden den Zustandsraum und können in Form eines Zustandsgraphen dargestellt werden. Dabei werden die einzelnen Zustände mit den möglichen Operationen des Modells untereinander verknüpft. Ziel im Bereich der Softwaretechnik ist es, diesen Zustandsgraphen auf Inkonsequenzen zu prüfen, womit gleichzeitig die Korrektheit des formalen Modells bewiesen werden kann. Dabei kann die Visualisierung des Zustandsgraphen helfen. Eine Möglichkeit dazu bietet das Programmpaket GraphViz an. Allerdings können Zustandsgraphen mit Graphviz nur zweidimensional dargestellt werden. Aus diesem Grund befasst sich diese Arbeit mit der Entwicklung eines Programms, welches die dreidimensionale Darstellung von Zustandsräumen formaler Modelle ermöglicht. Dazu wurde zuerst eine Methode entwickelt die Knoten eines, an sich dimensionslosen, Zustandsgraphen im dreidimensionalem Raum zu positionieren. Hierfür werden bestimmte Eigenschaften der Knoten wie z.B. deren jeweiliger Abstand zum Wurzelknoten oder die auf einen Knoten gerichteten Kanten als relevante Information zur Positionsbestimmung verwendet. Der darzustellende Zustandsgraph liegt in dem vom ProB Animator erzeugtem DOT Format vor. Für dieses Format wurde ein, komplett in JavaCC geschriebener, Parser in das Programm integriert. Der Rest des Programms wurde in Java geschrieben, wobei die eigentlich dreidimensionale Darstellung mit Hilfe der Java3D API erfolgt. Da Java3D auf das Konzept des Szenengraphen aufbaut, scheint es auf den ersten Blick ein idealer Ansatz zu sein, um Graphen zu visualisieren. Dies gilt allerdings nur für relativ kleine Zustandsgraphen. 5 Inhaltsverzeichnis Zusammenfassung............................................................................................................................4 1 Grundlagen.................................................................................................................................... 6 1.1 formale Modelle/B-Methode................................................................................................. 6 1.2 DOT.......................................................................................................................................7 1.3 JavaCC...................................................................................................................................8 1.4 Java3D................................................................................................................................... 8 1.4.1 Klassenhierarchie...........................................................................................................9 1.4.2 Szenengraph...................................................................................................................9 1.4.3 VirtualUniverse und SimpleUniverse..........................................................................11 1.4.4 Locale.......................................................................................................................... 11 1.4.5 BranchGroup................................................................................................................12 1.4.6 TransformGroup und Transform3D............................................................................ 13 1.4.7 Bound...........................................................................................................................13 1.4.8 Behavior.......................................................................................................................14 1.4.9 Java3D Beispielprogramm...........................................................................................15 2 B3D............................................................................................................................................. 17 2.1 Parser B3D_dot_parser........................................................................................................18 2.2 Allgemeine Darstellung des Zustandsgraphen.................................................................... 18 2.2.1 Knoten B3D_node....................................................................................................... 20 2.2.2 Kanten B3D_edge........................................................................................................21 2.2.3 Graph B3D_graph........................................................................................................23 2.3 Interaktion B3D_pick_behavior.......................................................................................... 24 2.4 Grafische Ausgabe B3D_viewer......................................................................................... 26 2.5 zukünftige Erweiterungen................................................................................................... 27 2.5.1 Scheibengraph- und Kugelgraphmodell...................................................................... 27 2.5.2 Performance Verbesserungen...................................................................................... 29 2.6 B3D Beispiele......................................................................................................................32 Quellenverzeichnis:........................................................................................................................34 allgemeine Quellen:....................................................................................................................... 34 Hilfsmittel:..................................................................................................................................... 34 Abbildungsverzeichnis:..................................................................................................................35 6 1 Grundlagen In diesem Abschnitt werden anhand von Beispielen die Grundlagen erläutert, welche das Verständnis des im 2. Abschnitt vorgestellten Programms erleichtern sollen. Dabei wird besonders auf die Funktionsweise und die Komponenten von Java3D Applikationen eingegangen. 1.1 formale Modelle/B-Methode Formale Modelle beschreiben die Funktionalität eines Objekts oder ein Verfahrens mittels einer eindeutig definierten formalen Sprache. Ein Modell beschreibt dabei eine Menge von Zuständen, die durch vordefinierte Operationen gewechselt werden können. Jeder Zustand ist dabei eindeutig. Die Menge aller Zustände und der entsprechenden Operationen bilden den Zustandsgraphen eines Modells. Das Ziel dabei ist es, diesen Zustandsgraphen auf seine Korrektheit hin zu überprüfen bzw. zu verifizieren, d.h. dass er keine unerlaubten Zustände enthält bzw. dass das Modell durch bestimmte Kombinationen von Operationen in einen nicht erwünschten Zustand wechselt. Formale Modelle finden besonders im industriellen Bereich z.B. in der Softwaretechnik ihre Anwendung, u.a. zur Prüfung der Funktionstüchtigkeit integrierter Schaltkreise vor deren Freigabe zur Serienfertigung. Ein Beispiel für eine formale Sprache ist die B-Notation, mit welcher sich formale Modelle in Form von B-Maschinen beschreiben lassen. Dieser Prozess wird auch B-Methode genannt. Die B-Methode wurde 1980 von Jean-Raymond Abrial in Frankreich entwickelt und spielt gerade im Bereich der Softwareentwickelung eine große Rolle. Eine B-Maschine, welche die Funktionalität eines Programms modelliert, kann mittels Refinements so weit „verfeinert“ werden, dass sie leicht in eine konkrete Programmiersprache übersetzt und somit direkt implementiert werden kann. Vorher muss die B-Maschine und jedes Refinement erst einmal selbst auf Korrektheit geprüft werden, um damit später fehlerfreie Software entwickeln zu können. Das folgende Beispiel einer B-Maschine stellt das Modell eines Fahrstuhls dar, welcher dem Benutzer über die Funktion auswahl die Auswahl eines bestimmten Stockwerks ermöglichen soll. MACHINE Fahrstuhl VARIABLES Etage_aktuell INVARIANT Etage_aktuell:0..5 INITIALISATION Etage_aktuell:=0 OPERATIONS auswahl(Etage_wahl)=PRE Etage_wahl:0..5 & Etage_wahl /= Etage_aktuell THEN Etage_aktuell:=Etage_wahl END END Die in VARIABLES deklarierte Variable Etage_aktuell repräsentiert die Etage, in der sich der Fahrstuhl zu einem bestimmten Zeitpunkt befinden kann. 7 Die Anzahl der Etagen ist im INVARIANTE Teil auf 6 beschränkt, d.h. Etage_aktuell ist ein Element aus der Menge {0,...,5}. In dem INITIALISATION Abschnitt wird die Ausgangsetage des Fahrstuhls auf das Erdgeschoss festgelegt. Mit der in OPERATIONS definierten Operation auswahl lässt sich die gewünschte Etage über deren Parameter Etage_wahl einstellen. Dabei werden in dem PRE ... THEN (Predicate) Teil die Grenzen des Parameters Etage_wahl festgelegt, damit durch die Operation die Invariante nicht verletzt werden kann, d.h. die gewählte Etage muss überhaupt einer vorhandenen Etage entsprechen und sie darf nicht die gleiche Etage sein, in der sich der Fahrstuhl gerade befindet. Im THEN ... END Teil steht die Anweisung, die ausgeführt wird sobald der Predicates Teil wahr ist, d.h. der Wert der Variable Etage_aktuell wird durch den Wert von Etage_gewählt überschrieben. Eine Möglichkeit B-Maschinen zu entwerfen und zu bearbeiten bietet das 2003 von M.Leuschel und M.Butler veröffentlichte Programm ProB an. Mit Hilfe des ProB Animators ist es möglich, die Zustände einer B-Maschine zu berechnen und in einem Zustandsgraph im DOT-Format abzuspeichern. 1.2 DOT DOT ist Teil des von AT&T und den Bell-Labs entwickeltem plattformübergreifendem OpenSource-Programmpakets GraphViz[1]. Mit der DOT-Sprache werden auf Kommandozeilenebene gerichtete sowie ungerichtete Graphen beschrieben und mit dem ebenfalls in GraphViz enthaltenem Programm DOTTY zweidimensional visualisiert. Die Sprachelemente von DOT erlauben eine Vielzahl an Formatierungen sowie Formen und Farben der Knoten und Kanten. Nachfolgend die Definitionen der DOT Sprache : graph : stmt_list : stmt : | | | | attr_stmt : attr_list : a_list : edge_stmt : edgeRHS : node_stmt : node_id : port : | subgraph : | compass_pt [ strict ] (graph | digraph) [ ID ] '{' stmt_list '}' [ stmt [ ';' ] [ stmt_list ] ] node_stmt edge_stmt attr_stmt ID '=' ID subgraph (graph | node | edge) attr_list '[' [ a_list ] ']' [ attr_list ] ID [ '=' ID ] [ ',' ] [ a_list ] (node_id | subgraph) edgeRHS [ attr_list ] edgeop (node_id | subgraph) [ edgeRHS ] node_id [ attr_list ] ID [ port ] ':' ID [ ':' compass_pt ] ':' compass_pt [ subgraph [ ID ] ] '{' stmt_list '}' subgraph ID : (n | ne | e | se | s | sw | w | nw) Die Schlüsselwörter node, edge, graph, subgraph, und strict sind Case unabhängig. Der Token ID kann aus Buchstaben, Zahlen und underscores bestehen. Des weiteren kann er auch escape Sequenzen, HTML Tags oder double quotes enthalten. 8 Der Token edgeop beinhaltet die Zeichen “->“ und “--“, die jeweils für eine gerichtete sowie eine ungerichtete Kante stehen. Abb.1 zeigt (links) die vom ProB Animator erzeugte DOT-Datei der in 1.1 gezeigten BMaschine. Der dazu passende Zustandsgraph (rechts) wurde mit DOTTY erzeugt. digraph visited_states { graph [page="8.5, 11",ratio=fill,size="7.5,10"]; root [shape=invtriangle, color="green", fontsize=12, label=""]; root -> 1 [style = dotted, color = black, label="initialise_machine(0)", fontsize=12]; 1 [shape=ellipse, color="green", fontsize=12, label="Etage_aktuell=0"]; 1 -> 2 [color = blue, label="auswahl(1)", fontsize=12]; 1 -> 3 [color = blue, label="auswahl(2)", fontsize=12]; 1 -> 4 [color = blue, label="auswahl(3)", fontsize=12]; 1 -> 5 [color = blue, label="auswahl(4)", fontsize=12]; 1 -> 6 [color = blue, label="auswahl(5)", fontsize=12]; 2 [shape=ellipse, color="green", fontsize=12, label="Etage_aktuell=1"]; 2 -> 1 [color = blue, label="auswahl(0)", fontsize=12]; 2 -> 3 [color = blue, label="auswahl(2)", fontsize=12]; 2 -> 4 [color = blue, label="auswahl(3)", fontsize=12]; 2 -> 5 [color = blue, label="auswahl(4)", fontsize=12]; 2 -> 6 [color = blue, label="auswahl(5)", fontsize=12]; 3 [shape=ellipse, color="green", fontsize=12, label="Etage_aktuell=2"]; 3 -> 1 [color = blue, label="auswahl(0)", fontsize=12]; 3 -> 2 [color = blue, label="auswahl(1)", fontsize=12]; 3 -> 4 [color = blue, label="auswahl(3)", fontsize=12]; 3 -> 5 [color = blue, label="auswahl(4)", fontsize=12]; 3 -> 6 [color = blue, label="auswahl(5)", fontsize=12]; 4 [shape=ellipse, color="red", fontsize=12, label="Etage_aktuell=3"]; 5 [shape=doubleoctagon, color="green", fontsize=12, label="Etage_aktuell=4"]; 5 -> 1 [color = blue, label="auswahl(0)", fontsize=12]; 5 -> 2 [color = blue, label="auswahl(1)", fontsize=12]; 5 -> 3 [color = blue, label="auswahl(2)", fontsize=12]; 5 -> 4 [color = blue, label="auswahl(3)", fontsize=12]; 5 -> 6 [color = blue, label="auswahl(5)", fontsize=12]; 6 [shape=ellipse, color="green", fontsize=12, label="Etage_aktuell=5"]; 6 -> 1 [color = blue, label="auswahl(0)", fontsize=12]; 6 -> 2 [color = blue, label="auswahl(1)", fontsize=12]; 6 -> 3 [color = blue, label="auswahl(2)", fontsize=12]; 6 -> 4 [color = blue, label="auswahl(3)", fontsize=12]; 6 -> 5 [color = blue, label="auswahl(4)", fontsize=12]; } Abb.1 Mit ProB Animator erzeugte DOT-Datei und der dazu passende Zustandsgraph 1.3 JavaCC JavaCC ist ein in Java geschriebener und Java Code erzeugender Parsergenerator. JavaCC ist ein OpenSource Projekt und steht unter der BSD_Lizenz. Im Gegensatz zu anderen Parsergeneratoren wie z.B. YaCC erzeugt JavaCC ein LL(k) TopDown Parser, d.h. dass der Programmierer beim Entwurf des Parsers Linksrekursionen vermeiden muss, da JavaCC diese nicht zulässt. Derzeit stehen eine Vielzahl von frei zugänglichen JavaCC Parsern für verschiedene Dateiformate und Grammatiken im JavaCC Format zu Verfügung[2]. Der für dieses Projekt verwendete DOT-Parser wurde komplett mit Hilfe des für die Eclipse IDE verfügbaren JavaCC Plugins geschrieben. 1.4 Java3D Java3D ist eine, seit 1997 von SUN entwickelte, plattformunabhängige High-Level API zur Darstellung und Manipulation von 3D Objekten und ganzen Szenen innerhalb von Java Applikationen und Applets. Seit 2004 ist Java3D als Open Source freigegeben und seit 9 Dezember 2006 als Version 1.5 verfügbar. Ab Version 1.4.1 bietet Java3D die Unterstützung von Shader-Programmen und ermöglicht somit auf modernen Grafikkarten komplexe Grafikeffekte. Javatypisch greift auch Java3D nicht direkt auf die Hardware zu, sondern setzt direkt auf die Low-Level API´s OpenGL bzw. Direct3D auf. Java3D ist nicht nur einfach eine Sammlung von Wrapperklassen, sondern abstrahiert die Funktionalität der darunter liegenden Schnittstelle in einem eigenem Objektorientiertem Konzept. Die OpenGL Version von Java3D existiert für fast alle gängigen Plattformen wie Windows, Linux, Mac OS und Solaris. Die Direct3D Version ist allerdings nur für Windows Plattformen verfügbar. Ziel von Java3D ist es, ohne Kenntnisse der Hardware schnell und einfach Szenen zu entwerfen, weshalb Java3D ähnlich wie VRML[3] auf das leicht verständliche Konzept des Szenengraphen aufbaut. Hauptbestandteile des Java3D Package sind java.media, welche sämtliche Bestandteile des Szenengraphen umfassen sowie java.vecmath, welches allgemeine mathematische Funktionen für Berechnungen mit Vektoren, Matrizen und Quaternions zu Verfügung stellt. 1.4.1 Klassenhierarchie Abb.2 zeigt in einem Ausschnitt aus der Java3D Klassenhierarchie die wichtigsten in diesem Projekt verwendeten Klassen. java.object javax.media.j3d.Locale javax.media.j3d.SceneGraphObject javax.media.j3d.Bounds javax.media.j3d.VirtualUniverse javax.media.j3d.Transform3D javax.media.j3d.Node javax.media.j3d.NodeComponent com.sun.j3d.utils.universe.SimpleUniverse javax.media.j3d.PickInfo com.sun.j3d.utils.pickfast.PickTool com.sun.j3d.utils.pickfast.PickCanvas javax.media.j3d.Group javax.media.j3d.Leaf javax.media.j3d.Appearance javax.media.j3d.Alpha javax.media.j3d.Geometry javax.media.j3d.TransformGroup javax.media.j3d.BranchGroup javax.media.j3d.Behavior javax.media.j3d.Shape3D javax.media.j3d.Text3D javax.media.j3d.GeometryArray com.sun.j3d.utils.universe.ViewingPlatform javax.media.j3d.OrientedShape3D com.sun.j3d.utils.behaviors.vp.ViewPlatformBehavior com.sun.j3d.utils.behaviors.vp.ViewPlatformAWTBehavior com.sun.j3d.utils.behaviors.vp.OrbitBehavior javax.media.j3d.Interpolator javax.media.j3d.GeometryStripArray javax.media.j3d.LineStripArray javax.media.j3d.TransformInterpolator com.sun.j3d.utils.behaviors.interpolators.KBSplinePathInterpolator Abb.2 Java3D Klassenhierarchie 1.4.2 Szenengraph Der Szenengraph von Java3D ist ein azyklischer gerichteter Graph, dessen Blätter, abgeleitet von 10 der Klasse Leaf, die komplette Szene, d.h. Szenengeometrie, Texturen, Beleuchtung, Interaktionen sowie die Eigenschaften der “Virtuellen Kamera“, beschreiben. Blätter, die in einem gemeinsamen Kontext zueinander stehen, werden durch Knoten vom Typ Group zusammengefasst. Die Kanten entsprechen Eltern-Kind Beziehungen oder Referenzierungen. Ein Knoten oder Blatt kann immer nur einen Vorgänger haben, ein Knoten kann aber beliebig viele Nachfolger haben. Alle Nachfolgerknoten und -blätter erben bestimmte Eigenschaften wie z.B. die Position einer TransformGroup ihres Elternknotens. Der Wurzelknoten jedes Szenengraphen ist ein Objekt der Klasse VirtualUniverse, an dem typischerweise ein aber auch mehrere Knoten vom Typ Locale angehängt sind. Ein Locale Knoten enthält einen oder mehrere BranchGroup Knoten, die jeweils einen kompletten Szeneninhalt darstellen und einen von BranchGroup abgeleiteten Knoten vom Typ ViewingPlatform, der sämtliche Komponenten der “Virtuelle Kamera“ beinhaltet. Dazu gehört unter anderem die ViewPlatform welche die eigentliche “virtuelle Kamera“ ist. Die Zugriffssteuerung auf sämtliche Knoten wird durch capability flags geregelt. Dabei hat jeder Knoten und jedes Blatt von der Superklasse geerbte und auch individuelle capability flags. Über die flags wird der Lese- und Schreibzugriff auf Objekte oder deren Eigenschaften zu Laufzeit geregelt. Dabei sollte der Programmierer darauf achten, dass wirklich nur die notwendigen flags gesetzt sind, da sonst zusätzlicher Overhead entsteht. Abb.3 zeigt einen Szenengraphen mit einem VirtualUniverse und einem angehängten Locale Knoten mit einem Szenen- und einem Viewzweig. Parent-Child Beziehung Referenzierung VirtualUniverse BG Locale VP Group TG S ... TG S VP BG BranchGroup TG TransformGroup VP ViewingPlatform View Leaf Behavior Canvas3D Geo App Szenenzweig Frame Viewzweig S Shape3D VP ViewPlatform Geo Geometry App Apperance Abb. 3 Szenengraph mit einem VirtualUniverse und einem angehängten Locale Knoten 11 1.4.3 VirtualUniverse und SimpleUniverse Das VirtualUniverse Objekt ist die Wurzel jedes Szenengraphen und direkt von java.object abgeleitet. Jede Java3D Applikation hat in der Regel nur ein VirtualUniverse Objekt, es können aber auch mehrere Objekte pro Anwendung vorhanden sein. Die Universen sind dann zwar koexistent können sich aber nicht gegenseitig beeinflussen. Ein VirtualUniverse ist nur ein Container, der die simulierte virtuelle (abstrakte) Welt enthält und an sich keine speziellen Eigenschaften hat. Somit muss der Programmierer sich immer noch selbst um den Rest des Szenengraphen und besonders um den Viewzweig kümmern. Diese Arbeit wird durch die von VirtualUniverse abgeleitete Klasse SimpleUniverse, welche sich in dem Package com.sun.java3d.utils befindet, abgenommen. SimpleUniverse stellt einen minimalen Szenengraphen mit einem Locale Objekt und komplettem Viewzweig zur Verfügung. Der Programmierer braucht dem Konstruktor nur noch einen Canvas3D für die eigentliche grafische Ausgabe übergeben und dem Locale Objekt einen Szenenzweig anhängen. Somit ist SimpleUniverse für einfache und schnelle Projekte gedacht und spart viel Zeit. Es ist zwar möglich, auf sämtliche Bestandteile des SimpleUniverse Szenengraphen zuzugreifen, allerdings verhindern die standardmäßig gesetzten capability-flags oft nachträgliche Änderungen am Viewzweig. 1.4.4 Locale Das Locale Objekt ist standardmäßig nur ein weiterer Container zur Gliederung von Objekten und auch eine direkte Ableitung von java.object.. Allerdings kann das Locale Objekt dazu verwendet werden, um allen darunter liegenden BranchGroup Objekten eine absolute Ausgangsposition im VirtualUniverse zu geben. Die Positionierung erfolgt über ein Objekt vom Typ HiResCoord, dessen Koordinaten pro Dimension 256Bit großen Festkommazahlen entsprechen. Die Koordinaten werden intern über drei Array´s der Länge 8, deren Einträge jeweils 32Bit Integerwerte enthalten, dargestellt. Getrennt werden die Festkommazahlen durch ein Komma zwischen dem 4. und 5. Eintrag des entsprechenden Array. Somit stehen jeweils 128Bit große Werte für Abstände größer und kleiner 1 zu Verfügung. Ein Wert von 1.0 entspricht dabei einem Abstand von genau 1 Meter vom Ursprung des VirtualUniverse. Die maximale Entfernung eines Locale vom Ursprung wäre also bei ~1.73*2128 m, (im Bezug auf die Raumdiagonale eines Würfels) wobei z.B. 200 Milliarden Lichtjahre gerade mal 287.29 m entsprechen. Die minimale unterscheidbare Länge entspricht 2-128 m und liegt somit weit unter der, aus der Physik bekannten, Planck Länge mit 2-155,57 m. Es wäre also gleichzeitig eine Simulierung auf markoskopischer und mikroskopischer Ebene möglich: Z.B. Protonen im Abstand von Millionen von Lichtjahren. Die relative Positionierung innerhalb eines Locale erfolgt entweder durch 32Bit float oder 64Bit double Werte pro Achse, womit die absolute Entfernung eines Punktes zum VirtualUnivers Ursprung auf ~1.73*2128m+1.73*264m*x ansteigt. Wobei x die Anzahl der auf der Raumdiagonale liegenden gestaffelten TranfromGoup´s darstellt. Das ermöglicht den Einsatz von Java3D besonders im wissenschaftlichem Bereich mit einer extrem hohen Genauigkeit. Allerdings kann der Betrachter nicht alle vorhandenen Locale´s gleichzeitig betrachten da der Viewzweig, welcher die “virtuelle Kamera“ darstellt, immer nur an ein Locale gleichzeitig 12 gehängt werden kann. Deshalb müsste der Viewzweig jeweils an das entsprechende Locale angehängt werden, dessen Inhalt betrachtet werden soll. Abb. 4 zeigt die Verwendung von mehreren Locale Objekten in einem VirtualUniverse: Zentrum des VirtualUniverse Locale2 Locale1 mehrere Lichtjahre Abstand y y z X z X an Locale1 angehängter Canvas3D/Viewzweig Betrachter Abb.4 Verwendung mehrerer Locale Objekte in einem VirtualUniverse 1.4.5 BranchGroup BranchGroup Objekte sind von der Klasse Group abgeleitet und bilden die Spitze jedes Szenenund Viewzweiges. Sie dienen wie jede von Group abgeleitete Klasse zur Zusammenfassung von Knoten mit gleichem Kontext: Z.B. eine komplette Szene oder Teile einer Szene. BranchGroup Objekte dürfen als einzige Knoten direkt an ein Locale Objekt angehängt werden. Während der Laufzeit ist es nur möglich, ein BranchGroup Objekt aus dem SceneGraphen auszuhängen, wenn dessen ALLOW_DETACH capability gesetzt ist. Somit ist es möglich, während die Java3D Anwendung läuft, komplette Szenen zu laden, ohne jedesmal ein komplett neues VirtualUniverse generieren zu müssen. Man hängt einfach einen alten Szenenzweig aus und einen neuen ein. An ein BranchGroup Knoten können beliebig viele Knoten vom Typ Group oder Blätter vom Typ Leaf , wie z.B Behaviors oder Shape3D, angehängt werden. Eine besondere Methode von BranchGroup ist compile(). Damit optimiert Java3D den Szenenzweig intern und die BranchGroup erhält den status “compiled“. Von diesem Zeitpunkt an kann auf die Blätter und Knoten einer BranchGroup weder lesend noch schreiben zugegriffen werden, es sei denn, die Knoten haben die entsprechenden capabilities gesetzt. Während der Optimierung werden z.B. mehrere hintereinander ausgeführte Bewegungsoperationen in einer Matrix zusammen gefasst. Für Objekte mit gleicher Geometrie wird das GeometrieObjekt durch eine Referenz auf ein globales GeometrieObjekt ersetzt, um Speicherplatz zu schaffen. 13 1.4.6 TransformGroup und Transform3D TransformGroup Objekte sind direkt von der Klasse Group abgeleitet und dienen zur Zusammenfassung und zum Positionieren von darunter hängenden Knoten bzw. Blättern. Jede TransformGroup enthält automatisch ein Objekt vom Typ Transform3D, welches die Positionsinformationen speichert. Mit einem Transform3D Objekt können alle möglichen Translationen, Rotationen um beliebige Achsen und Skalierungen realisiert werden. Die interne Darstellung erfolgt durch eine 4x4 floating point Matrix. Die linke obere 3x3 Matrix beschreibt Rotationen, die ersten drei Elemente des letzte Spaltenvektors Translationen und das letzte Element des Vektors den Skalierungsfaktor. Jede TransformGroup besitzt damit ihr eigenes lokales Koordinatensystem, dessen absolute Position und Ausrichtung im VirtualUniverse relativ zu dem Ursprung der darüber liegenden TransformGroup bzw. Locale liegt. Das bedeutet, die Auswirkungen von Bewegungsoperationen mehrerer untereinander angeordneter TransformGroup Objekte sind kumulativ. Dabei spielt die Reihenfolge der hintereinander ausgeführten Bewegungen eine Rolle: Es macht einen Unterschied, ob ein Objekt erst gedreht und dann verschoben wird oder anders herum. Dieser Unterschied wird in Abb. 5 verdeutlicht. 1. Translation 2. Rotation 1. Rotation 2. Translation Ausgangssituation Abb. 5 Auswirkung der Reihenfolge von Bewegungsoperationen auf TransformGroup Objekte Auf das interne Transform3D Objekt einer TransformGroup kann während der Laufzeit nur durch das Setzen der capabilities ALLOW_TRANSFORM_READ oder ALLOW_TRANSFORM_WRITE zugegriffen werden. Das ist dann notwendig, wenn z.B. ein Behavior während der Laufzeit die Position einer TransformGroup, und damit die der Objekte innerhalb bzw. unterhalb der TransformGroup, auslesen oder verändern möchte. 1.4.7 Bound Bound ist eine abstrakte von java.object abgeleitete Klasse. Ein Bound Objekt beschreibt den Wirkungsbereich eines Objektes oder einer Aktion. Die Unterklassen von Bound stellen mehrere verschiedenen Formen von Bound Objekten zur Verfügung. Wobei die wichtigste Subklasse 14 wohl die BoundigSphere darstellt. Außerdem wird von Java3D noch eine BoundingBox sowie ein BoundingPolytope zu Verfügung gestellt. Letzteres ist in seiner Ausdehnung über ein Array bestehend aus Vector4d Objekten frei gestaltbar. Eines der wichtigsten Objekte eines Szenengraph ist die BoundingSphere, welche die ViewingPlatform umgibt. Behavior werden erst aktiviert, wenn die Schnittmenge der BoundingSphere der ViewPlatform und des SchedulingBound des Behavior größer gleich 0 ist. 1.4.8 Behavior Die abstrakte Klasse Behavior stellt eine Reihe von Subklassen zur ereignisgesteuerten Manipulation bzw. Animation von Group Objekten und damit der angehängten Leaf´s zur Verfügung. Ereignisse, die ein Behavior auslösen, sind z.B. Interaktionen des Nutzers mit Maus, Tastatur oder anderen Eingabegeräten, die Kollision von Bounds, anderer Behavior Objekte oder sonstige benutzerspezifizierte Ereignisse. Jedes Bahavior hat einen durch ein Bound Objekt eingegrenzten Wirkungsbereich. Die Subklassen von Bahavior erben die abstrakten Methoden initialize() und processStimulus(). Einer der wichtigsten Subklassen bildet die Interpolator Klasse. Die abstrakte Subklasse Interpolator bietet Klassen um bestimmte Eigenschaften wie Farbe, Position oder Transparenz über eine beliebige Anzahl von Werten zu interpolieren. Die meisten Subklassen bieten dafür hauptsächlich lineare Interpolation oder kubische Spilneinterpolation angeboten. Die meisten Interpolator Objekte benötigen einen Bezug zur Zeit. Dies geschieht über ein Alpha Objekt, welches mittels eines Taktsignals, beginnend mit der aktuellen Systemzeit, einen Interpolator steuert. Dabei lässt sich das Signal beliebig in Frequenz oder Impulsdauer verändern. So kann man z.B. zeitlich abhängige weiche Farb- oder Transparenzübergänge schaffen. Hauptsächlich werden Interpolatoren aber zur Animation, d.h. zur zeitlichen Veränderung der Positions- und Formeigenschaften verwendet. Eine dafür häufig verwendete Subklasse ist KBRotPosScaleSplinePathInterpolator. Diese Klasse verwendet ein Array von Objekten vom Typ KBKeyframe, welche die Koordinaten der Stützstellen sowie die Ausrichtung auf den Achsen speichern. Anhand dieser Stützpunkte berechnet die Klasse KBRotPosScaleSplinePathInterpolator einen Pfad auf dem sich eine TransformGroup und damit deren angehängte Subgraphen animiert werden. Das Behavior bzw. die Animation kann jederzeit mit der Methode setEnable (Boolean enable) gestartet werden. Eine weitere wichtige von Behavior abgeleitete Klasse ist DistanceLOD. LOD steht dabei für “Level of Detail“. Ein DistanceLOD Objekt dient also zur Regelung der Detailtiefe, einer Szene in Abhängigkeit der Entfernung zur “virtuellen Kamera“. Dabei wird dem DistanceLOD Objekt zunächst ein Switch Objekt übergeben, welches sich wie eine BranchGroup verhält, nur dass es mehrere alternative Teilgraphen enthält. Normalerweise beinhalten die Teilgraphen eine Szene in verschiedenen vorberechneten Detailstufen. Über ein Array von float Werten wird dem DistanceLOD Objekt dann mitgeteilt, bei welcher Entfernung welcher Teilgraph des Switch Objektes in den Szenengraph eingehängt und somit angezeigt werden soll. 15 1.4.9 Java3D Beispielprogramm Dieses Beispiel soll den Aufbau eines typischen Java3D Programms erläutern. Das Programm gibt einen Text „Hallo Welt!“ in Form eines Text3D Objektes aus und bietet die Möglichkeit, mit der Maus die Kamera um den Nullpunkt dieses Objektes zu drehen und zu zoomen. Im Konstruktor der Hauptklasse hallo_welt wird in Zeile 6 zunächst das Objekt config vom Typ GraphicConfiguration angelegt und mit Hilfe der Methode getPrefferredGraphicConfiguration mit Standardinformationen zur verwendeten Grafikhardware gefüllt. Danach wird in Zeile 8 und 9 ein Canvas3D Objekt erzeugt, über den Konstruktor mit den Informationen, die in config enthalten sind, konfiguriert und dem Ausgabeframe hinzugefügt. Dem SimpleUniverse Objekt universe wird in Zeile 11 der canvas3d übergeben, womit festgelegt wird, auf welcher Zeichenfläche die grafische Ausgabe überhaupt stattfindet. In Zeile 14 wird ein Transform3D Objekt generiert, welches dazu dient, die ViewPlatform des universe Objektes um 10m vom Nullpunkt nach hinten zu verschieben. Damit kann die “virtuelle Kamera“(ViewPlatform) das nachher erzeugt Objekt überhaupt sehen und befindet sich nicht mitten im Objekt. Die Matrix des Transform3D Objekts wird deshalb invertiert, da dies eine Translation der ViewPlatform ist und keine Positionsänderung eines normalen Objektes im VirtualUniverse. Wenn sich z.B. die “virtuelle Kamera“ um 10m nach oben auf der y-Achse bewegt, bewegt sich das Objekt für den Betrachter allerdings um 10m nach unten. Diese umgekehrte Relation wird durch die Invertierung der Matrix der ViewPlatform richtig gestellt. In Zeile 20 wird ein Behavior vom Typ OrtibBehavior erzeugt, welches für die eigentliche Mausnavigation zuständig ist. Der Betrachter kann mit Hilfe von Mausbewegungen in einem Orbit um den Ursprung des Objektes kreisen. Mit dem Mausrad wird das Zoomen ermöglicht. Dem Konstruktor wird der Canvas3D übergeben, damit das OrtibBehavior die Mausbewegungen auf der Zeichenfläche ermitteln und somit die gewünschte Bewegung berechnen kann. Sobald also die Maus bewegt wird, wird ein MouseEvent ausgelöst, der das OrtibBehavior aufweckt. Die Mausbewegung auf dem Canvas3D wird interpoliert und die berechnete Transformation wird direkt auf das Transform3D Objekt der ViewingPlatform angewandt. Der Wirkungsbereich des OrtibBehavior wird durch die in Zeile 21 und 22 erzeugte und zugewiesene BoundingSphere auf einen Radius von 100m begrenzt, d.h. zoomt man mehr als 100m vom Ursprung weg, wird das OrtibBehavior nicht mehr aktiviert und der Benutzer kann auch nicht mehr zurück zoomen, um die ViewingPlatform wieder in die BoundingSphere zu bewegen. In den Zeilen 25 bis 27 wird das einzige sichtbare Objekt der VirtualUniverse erzeugt. Zunächst wird ein Font3D Objekt generiert, welches unter anderem festlegt, welche Schriftart und welche Größe das Text3D Objekt überhaupt haben soll. Der 3. Parameter des Font3D Konstruktors bestimmt den Schwellwert, der bei der Tesselation[4] der, durch Extrusion[5] der an sich zweidimensionalen Schrift, erzeugten Geometrie benutzt wird. Er gibt einen Wert für die Anzahl der Polygone und damit die grafische Darstellungsqualität der erzeugten Schrift an. Je niedriger der Schwellwert desto feiner wird der Text aufgelöst. Danach wird ein einfaches Group Objekt erzeugt, welchem das Text3D Objekt mit der Methode addChild() angehängt wird. Da Text3D nicht von der Klasse Leaf abgeleitet wurde, sondern von 16 der Klasse Geometry, muss das Text3D Objekt in ein Shape3D Objekt gepackt werden, dessen Konstruktor ein Objekt der Klasse Geometrie erwartet. Dieses Group Objekt wird an eine BranchGroup angehängt, welche in Zeile 35 an das Locale Objekt des SimpleUnivers gehängt wird. In der main Methode wird dann ein neues Objekt der Klasse hallo_welt instanziert und die von Frame geerbte Eigenschaft, die den Frame erst anzeigt, mit der Methode setVisible(boolean bool) auf true gesetzt. Java-Beispiel-Programm „Hallo-Welt“: 1 public class hallo_welt extends Frame { 2 3 public hallo_welt (String title) { 4 super(title); 5 6 GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration(); 7 8 Canvas3D canvas3d = new Canvas3D (config); 9 add(canvas3d); 10 11 SimpleUniverse universe = new SimpleUniverse(canvas3d); 12 13 14 Transform3D transform3d = new Transform3D(); 15 transform3d.setTranslation(new Vector3d(0,0,-10)); 16 transform3d.invert(); 17 universe.getViewingPlatform().getViewPlatformTransform().setTransform(transform3d); 18 20 OrbitBehavior orbit = new OrbitBehavior(canvas3d,OrbitBehavior.REVERSE_ALL); 21 BoundingSphere bounds = new BoundingSphere(new Point3d(),100.0); 22 orbit.setSchedulingBounds(bounds); 23 universe.getViewingPlatform().setViewPlatformBehavior(orbit); 24 25 Font3D font3d =new Font3D(new Font("Arial", Font.PLAIN, 1),10, new FontExtrusion()); 26 Text3D text3d = new Text3D(font3d,"Hallo Welt!" ); 27 text3d.setAlignment(Text3D.ALIGN_CENTER); 28 29 Group group= new Group(); 30 grou.addChild(new Shape3D(text3d)); 31 32 BranchGroup branchgroup = new BranchGroup(); 33 branchgroup.addChild(transroot); 34 35 universe.addBranchGraph(branchgroup); 36 } 37 38 public static void main(String args[]) { 39 hallo_welt beispiel = new hallo_welt("Beispiel"); 40 beispiel.setSize(600,400); 41 beispiel.setVisible(true); 42 } 43} Abb. 6 zeigt zur Verdeutlichung links die graphische Ausgabe des Programms und rechts den entsprechenden Java3D Szenengraph. universe branchgroup BG BG ViewingPlatform orbit group text3d TG S VP font3d Abb. 6 Graphische Ausgabe und dazugehöriger Java3D Szenengraph des Beispielprogramms „Hallo-Welt“ 17 2 B3D Ziel des Programms B3D ist es, ein formales Modell dreidimensional darzustellen und mit ihm zu interagieren. Das formale Modell wird zunächst als B-Maschine spezifiziert und mit ProB animiert. Der so entstandene Zustandsgraph wird von ProB in einer Datei im DOT Format abgespeichert. Diese Datei wird von B3D geparst, wobei der B3D_parser nur die relevanten Informationen über die Knoten und Kanten des Graphen ausliest, welche für eine dreidimensionale Positionierung notwendig sind. Die so erhaltenen Informationen werden von der Klasse B3D_graph mit Hilfe der Klassen B3D_node und B3D_edge zu einem kompletten dreidimensionalen Modell des Zustandsgraphen zusammengefügt und über die Klasse B3D_viewer auf dem Bildschirm ausgegeben. Die Interaktionen, wie z.B. die Bewegung der “virtuellen Kamera“ sowie das auswählen verschiedener Knoten und Kanten mit Hilfe der Maus, übernimmt hauptsächlich die Klasse B3D_pick_Behavior. Die allgemeine Vorgehensweise wird in Abb. 7 schematisch dargestellt. ProB B3D Formales Modell B-Maschine Ausgabe Dialoge B3D_viewer 3D-Graph B3D_graph Interaktion B3D_pick_behavior Knoten Animator Parser DOT-Datei B3D_parser Kanten B3D_node B3D_edge Abb. 7 Allgemeine Vorgehensweise des Programms B3D In den folgenden Abschnitten werden die einzelnen Komponenten von B3D sowie deren Funktionalitäten erläutert. 18 2.1 Parser B3D_dot_parser Der hier verwendete Parser ist mit JavaCC geschrieben worden und hat die Aufgabe, die für den Graphen relevanten Informationen aus der von ProB erstellten DOT-Datei heraus zu filtern. Die gefilterten Informationen wie ID bzw. Name der Kanten oder Knoten sowie deren Eigenschaften in Form von label werden der B3D_graph Klasse über zwei Funktionen übergeben. Die Funktionen add_node_label (String ID,String label) und add_nodelabel(String ID_from,StringID_to,String label) bilden dabei die einzige Schnittstelle zur B3D_graph Klasse. Dem Parser wird über einen erweiterten Konstruktor der Dateiname der zu parsenden DOT-Datei in Form eines Strings übergeben. Mit diesem String wird ein FileStream Objekt erzeugt, welches dem Standardkonstruktor des JavaCC Parsers übergeben wird. Der Tokenizer zerlegt diesen Stream in die unter 1.3. definierten Token der DOT-Sprache. Dabei überspringt der eigentliche Parser die für die grafische Ausgabe mit DOTTY relevanten Token, wie z.B. die Form oder Farbe der Knoten. Wenn der Parser die Definition eines Knoten erkennt, wird die ID des Knotens zwischengespeichert und bis zum label Token weiter geparst. Der Token, der direkt auf das label Token folgt, wird zusammen mit der ID des Knotens über die Funktion add_node_label(String ID,String label) an die B3D_graph Klasse übergeben. Der Vorgang verläuft genauso, falls der Parser die Definition einer Kante entdeckt. Die ID links vom edgeop Token entspricht dabei dem Startknoten und die ID rechts vom edgeop Token entspricht dem Endknoten. Die ID´s sowie das in der gleichen Zeile geparste label werden über die Funktion add_nodelabel() an die Klasse B3D_graph übergeben. Das Parser Objekt wird als static deklariert da, in diesem Fall auch nur wirklich ein Parser auf einmal zur Laufzeit benötigt wird. Das erleichtert dem Garbagecollector der JavaVM ein wenig die Arbeit, da es insgesamt nur ein Parser Objekt Pro Applikation geben kann. Falls ein neuer Parser gebraucht wird, wird statt des new Operators die Funktion ReInit() aufgerufen, welche unter anderem das FileStream Objekt überschreibt und andere interne Objekte des Parsers zurücksetzt. 2.2 Allgemeine Darstellung des Zustandsgraphen Die Zustände eines formalen Modells sowie dessen Operationen sind an sich dimensionslos, besitzen also keinen Bezug zum dreidimensionalen Raum. Deshalb ist das Hauptproblem erst einmal, die Eigenschaften dieser Zustände (im weiterem Verlauf Knoten genannt) und Operationen (Kanten) heraus zu filtern, die für eine eindeutige Positionierung des formalen Modells im dreidimensionalem Raum relevant sind. Knoten haben dabei die Eigenschaften, die den Zustand eines Modells an sich beschreiben. Sie werden in der durch ProB erstellten DOT-Dateien durch die label dargestellt. Da diese Eigenschaften aber zum Kontext des Modells gehören, lässt sich diese Information schlecht verallgemeinern, um auch in anderen Modellen einen Bezug auf die Position eines Knoten im Raum herzustellen. Eine weitere allgemeine Eigenschaft, mit der sich Knoten gruppieren lassen, ist deren „Abstand“ (im Folgenden distance Wert) zum root-Knoten. Mit Abstand ist hierbei die minimale Anzahl der Knoten, welche zwischen dem root- und dem Endknoten auf einem Pfad liegen, gemeint. Anhand dieses Wertes lassen sich die Knoten schon einmal in einer Dimension gliedern. Ein weiterer Vorteil dieser Anordnung ist, dass der Zustandsgraph automatisch baumartig angeordnet wird. 19 Eine weitere kontextunabhängige Eigenschaft eines Knoten ist der „Typ“ der Kanten, die auf ihn zeigen. Mit „Typ“ ist dabei die unterscheidbare Art von Kanten gemeint, welche einer Operation des formalen Modells entsprechen. Dabei spielen der Name oder die Parameter der Operation keine Rolle sondern nur die Tatsache, dass verschiedene Operationen vorhanden sind. Mit dieser Eigenschaft kann die Position eines Knoten auf einer Scheibe bzgl. ihres Mittelpunktes allein durch die auf den Knoten zeigenden Kanten bestimmt werden. Dazu wird der Vollkreis (360°) durch die Anzahl der verschiedenen Operationen des formalen Modells geteilt. Somit wird jeder Operation ein eindeutiger Winkel auf einer Ebene zugeteilt. Zeigt nur eine Kante auf einen Knoten, befindet er sich in dem der Operation entsprechendem Abschnitt auf der Fläche. Zeigen mehrere Kanten unterschiedlicher Art auf einen Knoten, so werden die Position der Segmente der Operationen gemittelt, um die endgültige Position des Knotens auf der Fläche zu bestimmen. Durch diese beiden Eigenschaften wäre es schon möglich, einem Knoten mittels Polarkoordinaten eine Position zuzuweisen. Es gibt natürlich noch mehr Eigenschaften, die zur Positionierung herangezogen werden könnten. Auf diese wird hier jetzt aber nicht näher eingegangen. Das im Folgenden vorgestellte Kegelgraphmodell beruht auf den oben genannten Eigenschaften und definiert für jeden Knoten anhand dieser Eigenschaften eine eindeutige Position im Raum. Der Name dieses Modells beruht darauf, dass durch die Anordnung der Knoten nach diesem Verfahren eine kegelförmige oder auch baumartige Struktur entsteht. Diese Struktur ist rekursiv aufgebaut, d.h. alle direkten Nachfolger eines Knoten werden auch wieder in Form einer kegelförmigen Struktur an diesen angehängt usw.. Der distance Wert eines Knotens bestimmt dabei direkt die Position auf der Y-Achse und gruppiert die Knoten somit in verschiedene Ebenen. Die Position eines Knoten auf einer Ebene ist abhängig von dem übergeordneten Knoten und der Kante, welche beide verbindet. Falls mehrere unterschiedliche Kanten auf einen Knoten zeigen, wird nur die erste, die geparst wurde, zur Positionsbestimmung herangezogen. Die absoluten Koordinaten eines Knoten berechnen sich dann wie folgt: x= Re∗sin k x v y=Re∗sin k y v z =Re z v Dabei entspricht e dem distance Wert bzw. der Ebene auf der sich der Knoten befindet. Re ist somit der Radius der Kegel der e. Ebene und k ist die oben genannte eindeutige Abbildung einer Kante k auf einen Winkel. Die werte xv , yv und zv entsprechen der Position des direkten Vorgängerknotens. Der Radius Re berechnet sich über e und den Radius der ersten Ebene. Re =R1 /e R1 wird so gewählt, dass die Knoten der Kegel der letzten Ebene einen Mindestabstand zueinander haben. R1=t∗q∗D/2∗ t entspricht dabei der letzten Ebene, q ist die Anzahl der unterscheidbaren Kanten, legt also fest wie viele Knoten sich maximal in einem Kegel der letzten Ebene befinden können. D ist der “Durchmesser“ eines Knotens inkl. Mindestabstand. 20 Abb.8 zeigt den schematischen Aufbau eines Kegelgraphen mit zwei Ebenen und drei verschiedenen Operationen (a,b und c). a b c a Ebene 1 R1 a a b c c Ebene 2 R2 Abb. 8 Schematischer Aufbau eines Kegelgraphen mit zwei Ebenen und drei verschiedenen Operationen (a,b und c) 2.2.1 Knoten B3D_node Die Klasse B3D_node gehört zum Package B3D_graph und ist eine Erweiterung der Klasse TransformGroup. Jedes Objekt der Klasse stellt einen Knoten des formalen Modells dar, welcher direkt während der Aufbauphase in die BranchGroup des resultierenden Graphen eingehängt wird. Jedes B3D_node Objekt besitzt eine eindeutige interne ID, die der Identifizierung der Knoten während der Aufbaus des Graphen dient. Diese ID wird durch die DOT-Datei vorgegeben und spielt keine Rolle bei der grafischen Ausgabe. Jeder Knoten enthält ein Array edges_from(), das Referenzen auf Objekte vom Typ B3D_edge beinhaltet, welche wiederum weitere B3D_node Objekte referenzieren. Damit werden während der Parsingphase die ausgehenden Kanten und die Zeilknoten gespeichert. Der Graph des formalen Modells wird also erst einmal dimensionslos im Speicher als Referenzgraph aufgebaut. Obwohl dadurch eigentlich Zyklen im Szenengraphen entstehen, wird das Prinzip des Szenengraphen nicht verletzt, da diese Verknüpfungen auf einer anderen Abstraktionsebene existieren und keinerlei Einfluss auf den Java3D Szenengraphen oder die grafische Ausgaben an sich haben. Eine wichtige Variable eines B3D_node Objektes stellt der distance Integerwert dar. Er entspricht, wie in 2.2 beschrieben, der kürzesten Entfernung zum Wurzelknoten in Anzahl der Knoten, die dazwischen liegen einschließlich des aktuellen Knoten. Der distance Integerwert 21 bestimmt somit die Ebene des Graphen, in der der Knoten positioniert wird. Bis auf den Wurzelknoten haben somit alle folgenden Knoten einen distance Wert größer 0. Im Konstruktor der B3D_node Klasse wird die distance anfangs auf einen sehr hohen Wert gesetzt und mit der Methode set_distance (int new_distance) aktualisiert. Allerdings wird die distance Variable nur mit dem neuen Wert überschrieben, wenn dieser kleiner ist, da die minimale Entfernung zum Wurzelknoten gesucht wird. Die Eigenschaften jedes Knoten, die der Parser in der DOT-Datei hinter dem Token „label“ findet, werden in einem String label gespeichert. Diese Eigenschaft hat direkten Einfluss auf die grafische Darstellung, da dieser String im resultierenden Graphen für jeden Knoten in Form eines Text3D Objektes grafisch dargestellt wird. Das Besondere an dem Text3D Objekt ist, dass es sich automatisch zur ViewingPlatform ausrichtet und immer senkrecht, zu einer gedachten vom Betrachter ausgehenden Linie, steht. Dies wird erreicht, indem die Geometrie des Text3D Objektes nicht an ein gewöhnliches Shape3D sondern in ein OrientedShape3D Objekt übergeben wird. Da es der Übersichtlichkeit und, bei großen Graphen, der Performance schadet, wenn die label aller Knoten gleichzeitig angezeigt werden, besitzt jeder Knoten zusätzlich ein distanceLOD Objekt, welches automatisch je nach Entfernung des Betrachters zum Knoten entweder ein Sphere Objekt oder das label in Form eines Text3D Objektes in den Szenengraph einhängt und somit anzeigt. Die Speicherung und Auswahl der zwei verschieden Objekte übernimmt ein Switch Knoten, welcher dem distanceLOD Objekt übergeben wird. Das distanceLOD Objekt erhält ein float Array, in dem die Entfernungen, bei denen das Switch Objekt umschalten soll, abgelegt sind. Jedes B3D_node Objekt besitzt eine Variable coord vom Typ Vector3d, welche die Koordinaten des Knotens speichert. Während der Parsingphase spielt die Variable keine Rolle und bleibt solange leer, bis der Knoten in der Aufbauphase bearbeitet wird. Erst dann wird über die Funktion place_node() die endgültige Position jedes Knotens im resultierendem Graphen festgelegt, indem das Transform3D Objekt des Knotens selber mit den über die Methode set_coord() übergebenen Koordinaten überschrieben wird. 2.2.2 Kanten B3D_edge Die Klasse B3D_edge gehört, wie B3D_node, zum Package B3D_graph, ist aber im Gegensatz zu diesem eine direkte Erweiterung der Klasse Group. In diesem Fall wurde die Positionierung eines B3D_edge Objektes, aus Performancegründen, nicht über eine TransformGoup realisiert. Mehr dazu siehe Abschnitt 2.4.4. Objekte vom Typ B3D_edge repräsentieren die Operationen eines formalen Modells. Die vier Arten von Kanten werden wie folgt dargestellt: Kanten von einer höheren zu einer darunter liegenden Ebene sowie Kanten auf der gleichen Ebene aber zu unterschiedlichen Knoten werden durch eine Gerade verbunden. Hierbei kann es allerdings, gerade bei Verbindungen innerhalb einer Ebene, zu sehr vielen Überschneidungen kommen. Kanten, die Knoten verschiedener Ebenen verbinden, sowie Kanten, deren Start und Endknoten identisch sind, werden durch einen kubischen Spline dargestellt. 22 Geraden- und Splinekanten durchlaufen jeweils drei Punkte: Start- und Endpunkt sowie einen weiteren mittleren Stützpunkt, der durch mehrere Vektoren berechnet wird, wie auf Abb.9 zu sehen ist. a=from+to Y from middle=b + e Z b=(|a| / 2)*a e=d x c to X d=c x y c=to-from Abb.9 Darstellung des Verlaufs der Geraden- und Splinekanten durch Start- und Endpunkt sowie den mittleren Stützpunkt from, to, a,b,c,d,e und middle sind jeweils Vektoren. Alle im Bild gezeigten Operationen beziehen sich auch auf Vektoren, so ist |a| der Betrag oder die Länge vom Vektor a. “*“ bezieht sich auf die Multiplikation mit einem Skalar und “x“ steht für das Kreuzprodukt zweier Vektoren. Die Vektoren from und to sind die Ortsvektoren des Start- bzw. Endknotens. Für den Fall, dass die Kante als Gerade dargestellt werden soll, verläuft ein linear interpolierter Spline durch die Endpunkte der Vektoren from b und to. Der mittlere Stützpunkt einer durch einen kubischen Spline dargestellten Kante entspricht dem Punkt, auf den der Vektor middle zeigt. An der Position des Mittelpunktes einer Geraden (Vektor b) bzw. am Scheitelpunkt eines Splines (Vector middle) wird außerdem, ähnlich wie in der Klasse B3D_node, über ein Text3D Objekt das geparste label der Kante angezeigt. Aus Gründen der Performance und Übersichtlichkeit wird dieses Text3D Objekt auch hier mit Hilfe eines Switch Objektes und eines distanceLOD Behavior erst ab einer bestimmten Entfernung zum Betrachter eingeblendet. Da Java3D keine Klasse zur direkten Berechnung von Splines anbietet, musste auf indirekten Weg ein Spline über Klasse KBRotPosScaleSplinePathInterpolator, aus der Klasse Behavior, erzeugt werden. Eigentlich dient diese Klasse dazu, einen Pfad über mehrere Punkte entweder linear oder durch kubische Splines zu interpolieren, um dann mittels eines Alpha Timer Objektes eine TransformGroup und ihre angehängten Objekte entlang dieses Pfades zu animieren. Für die grafische Darstellung des Splines werden allerdings nur die interpolierten Punkte dieses Pfades benötigt. Dazu wird erst einmal pro Punkt ein KBKeyFrame Objekt erzeugt und mit den Koordinaten der Stützpunkte des Splines gefüllt. Die KBKeyFrame Objekte werden in einem Array gespeichert und an den Konstruktor des KBRotPosScaleSplinePathInterpolator Objektes übergeben. 23 Über die Methode KBRotPosScaleSplinePathInterpolator.computeTransform (float alpha,Transform3D tr3d) lässt sich mit Hilfe eines float Wertes alpha schrittweise jede einzelne Transformation der Animation über das Transform3D Objekt tr3d auslesen. Der alpha Wert wird aufsteigend in einer Schleife inkrementiert und der Translationsteil des entsprechenden Transform3D Objektes in einem Point3D Objekt gespeichert. Der Punkt aus dem vorangegangenem Schleifendurchlauf dient dann jeweils als Startpunkt und der Punkt des aktuellen Durchlaufes als Endpunkt einer Geraden. Alle so erzeugten Geraden werden in einem StripLineArray abgespeichert und als Geometriedaten an ein Shape3D Objekt übergeben, welches dann direkt an das B3D_edge Objekt angehängt wird. Je kleiner die Schrittweite in der der alpha Wert erhöht wird, aus desto mehr einzelnen Linien besteht der resultierende Spline und desto feiner aufgelöst ist er. Allerdings kann eine zu hohe Auflösung stark zu Lasten der Performance gehen. Deshalb ist es sinnvoll, mehrere Detailstufen eines Spline zu berechnen, an ein Switch Objekt zu hängen und mit Hilfe eines distanceLOD Behavior in Abhängigkeit vom Abstand des Betrachters darzustellen. 2.2.3 Graph B3D_graph Die Klasse B3D_graph ist für die eigentliche Konstruktion des Zustandsgraphen verantwortlich. Dabei läßt sich die Konstruktion in zwei wesentliche Phasen einteilen: Der Parsingphase und die Aufbauphase. In der Parsingphase wird der Zustandsgraph erst einmal dimensionslos als Referenzengraph im Speicher aufgebaut. Dazu wird der in 2.1 erwähnte Parser mit dem Pfad und dem Namen der DOT-Datei initiiert. Hierzu stellt die Klasse B3D_graph dem Parser die Methoden add_node_label(String ID,String label) und add_edge(String ID_from,String label,String ID_to) zur Verfügung. Die Methode add_node_label erstellt ein neues Objekt vom Typ B3D_node und übergibt dem Konstruktor die intern genutzte ID des Knoten und das geparste label. Eine Referenz auf das Knotenobjekt wird zeitgleich in der TreeMap TreeMap_nodes gespeichert. Als Schlüssel wird hier die eindeutige ID des Knotens benutzt. Die TreeMap arbeitet ähnlich wie eine Hashmap nach dem Prinzip des rot-schwarz Baumes[6] und dient zum schnellen Finden eines Knoten im Referenzengraphen mit einem Aufwand von O(log(n)). Ansonsten müsste die Struktur per Tiefensuche nach einem Knoten durchsucht werden, was einen erheblich größeren Aufwand zur Folge hat. Die Methode add_edge funktioniert ähnlich. Dabei stehen die Parameter ID_from und ID_to jeweils für den Ursprungs- bzw. den Endknoten einer Kante und label für den Namen der Kante. Zuerst wird anhand der TreeMap TreeMap_nodes überprüft, ob die Knoten mit ID_from und ID_to schon im Referenzengraph exsitieren. Falls dies nicht der Fall ist, werden die Knoten erst einmal über die Methode add_node_label mit einem leeren label erzeugt. Mit label und ID_to wird ein neues Objekt vom Typ B3D_edge erzeugt und in die interne Liste aller Kanten des Knotens ID_from hinzugefügt. Somit besitzt der Knoten ID_from eine logische Verbindung zum Knoten ID_to. Des weiteren wird in der Methode add_edge dem Knoten ID_to ein neuer distance wert zugewiesen. Dieser distance Wert entspricht dem des Knoten ID_from +1. Während der Parsingphase kann es passieren, dass ein Knoten B, der schon eine eingehende Kante und somit einen gültigen distance Wert besitzt, Ziel eines Knotens A mit einem noch 24 niedrigeren distance Wert wird. Der distance Wert von B entspricht dann, wie oben erwähnt, dem distance Wert von A+1. Wenn B allerdings direkte Nachfolger hat, können sich deren distance Werte auch ändern. Diese Änderung muss im gesamten Graph “geflutet“ werden. Die Methode propagate_distance(B3D_node,int distance), welche direkt nach jeder distance Wertzuweisung eines Knotens aufgerufen wird, übernimmt diese Aufgabe. Dabei wird die Änderung rekursiv, ab dem Knoten, dessen distance Wert sich geändert hat, im Graph verbreitet. Jedem rekursivem Aufruf der Methode propagete_distance wird ein Array von schon besuchten Knoten übergeben, um Endlosschleifen bei der Rekursion zu vermeiden, d.h. sobald ein Knoten erreicht wird, welcher schon in diesem Array gespeichert ist, wird die Rekursion abgebrochen. Wird ein Knoten erreicht dessen distance Wert kleiner oder gleich dem geflutetem distance Wert ist, wird die Rekursion an dieser Stellen abgebrochen und ein anderer Pfad ausprobiert. Ist das Parsing abgeschlossen und der Zustandsgraph als Referenzengraph aus B3D_node und B3D_edge Objekten realisiert, beginnt die die Sortierung der Knoten. Hierzu wird erst einmal eine neue TreeMap TreeMap_nodes_sorted mit einem Comparator vom Typ B3D_distance_comparator erzeugt. Der Comparator beginnt die Werte einer TreeMap automatisch nach bestimmten Kriterien zu sortieren, in diesem Fall aufsteigend nach den distance Werten der Knoten. Nachdem TreeMap_nodes_sorted erzeugt wurde, wird der Inhalt der TreeMap_nodes komplett in TreeMap_nodes_sorted kopiert und dabei automatisch geordnet. Hiermit ist die Parsingphase abgeschlossen. In der Aufbauphase werden den Knoten und Kanten erst die Koordinaten zugewiesen, die eine Positionierung im dreidimensionalem Raum ermöglichen. Der erste Knoten der TreeMap_nodes_sorted erhält die Koordinaten (0,0,0), alle folgenden Knoten werden in einer Schleife relativ zum diesem positioniert. Die Berechnung der Koordinaten erfolgt wie in 2.1 beschrieben anhand der Kante, die auf einen Knoten zeigt und dessen distance Wert. Die Funktion set_coords (Vector3d pos) der Klasse B3D_node erzeugt die eigentlich sichtbaren Objekte eines Knoten. Danach wird das B3D_node Objekt an die die Instanz von B3D_graph angehängt. Die Positionierung der Kanten erfolgt durch die Funktion draw_edge (B3D_node from). from ist hier der Ausgangsknoten der Kante. Zusammen mit der in dem B3D_edge Objekt gespeicherten Referenz auf den Zielknoten kann die Kante gezeichnet werden, indem die Koordinaten beider Knoten verwendet werden. Hierbei erfolgt eine Fallunterscheidung der Kanten: - Sind beide Knoten identisch, handelt es sich um einer selbstreferenzierende Kante. - Ist der distance Wert des Startknoten größer als der des Endknotens oder sind beide gleich groß, handelt es sich um eine Kante des größten Teilbaumes des Graphen. - Ist dagegen der distance Wert des Endknoten größer als der des Startknotens, handelt es sich bei der Kante um eine rücklaufende Kante(außerhalb des baumartigen Teils des Graphen). Diese drei Arten von Kanten werden in der Aufbauphase jeweils an unterschiedliche BranchGroups gehängt, um diese später bei Bedarf durch Aushängen aus der Wurzel des Szenezweiges auszublenden. 2.3 Interaktion B3D_pick_behavior Die Interaktion mit dem dreidimensionalem Graphen erfolgt ausschließlich mit der Maus. Für die Bewegung der “virtuellen Kamera“ ist ein OrtibBehavior Objekt verantwortlich, welches auf die gleiche Art und Weise, wie in dem Beispielprogramm unter 1.5.11 aufgeführt, an die TransformGroup der ViewingPlatform angehängt wird. 25 Eine Drehung erfolgt bei gedrückter linker Maustaste und eine Translation bei gedrückter rechter Maustaste und gleichzeitiger Mausbewegung. Dabei sind standardmäßig die Freiheitsgrade der Drehung und Translation auf die Y-Achse beschränkt. Für Interaktionen, die die Auswahl spezifischer Knoten und Kanten durch die Maus betreffen, ist die Klasse B3D_pick_bahvior verantwortlich. Die Klasse ist eine direkte Ableitung der Klasse Behavior und erbt somit sämtliche Methoden und Eigenschaften. Die Implementierung der abstrakten Methoden initialize() und pricessStimulus() sind dabei dem Programmierer überlassen. Diese Klasse identifiziert die Knoten bzw. Kanten, über die sich der Mauscursor bewegt, und gibt die gewonnenen Informationen in Form eines Text3D Objektes in der Linken Oberen Ecke des Canvas3D aus. Zuerst wird dazu über die Methode initialize() das Ereignis festgelegt, welches das Behavior überhaupt aktivieren soll. Das Behavior soll starten, sobald die Maus bewegt wird oder eine Maustaste gedrückt wird. Dies geschieht durch die Methode wakeupOn(new WakeupOnAWTEvent(MouseEvent.MOUSE_PRESSED|MouseEvent.MOUSE_MOVED)) D.h. sobald in der AWTEventqueue ein Ereignis bearbeitet wird, dessen eventID entweder MouseEvent.MOUSE_PRESSED oder MouseEvent.MOUSE_MOVED entspricht, werden alle Behavior, die sich für diese Art von Ereignis über die wakeup Methode beim AWT Eventscheduler angemeldet heben, aktiviert. In diesem Projekt wären das das oben genannte OrtibBehavior und das Behavior vom Typ B3D_pickBehavior. Sobald das B3D_pick_Behavior gestartet ist, wird dessen processStimulus (Enumeration criteria) Methode ausgeführt. Die Aufzählung criteria enthält sämtliche Ereignisse, die dazu geführt haben, dass dass Behavior aktiviert wurde. Diese Aufzählung wird in einer Schleife durchlaufen, in der dann unterschiedliche Ereignisse wie z.B. MouseEvent.MOUSE_PRESSED oder MouseEvent.MOUSE_MOVED einzeln behandelt werden können. In Falle des Ereignisses, welches eine Mausbewegung ausgelöst hat, wird die eigentliche Auswahl (auch Picking genannt) von Objekten “unterhalb“ des Mauscursors gestartet. Der Vorgang des Picking läuft folgendermaßen ab: Von der Zeichenfläche (Canvas3D) führt ein senkrechter Strahl (Pickray) in den Raum. Die X-Y Koordinate des Mauscursors auf der Zeichenfläche bestimmt dabei die Ausgansposition des Pickray. Alle Objekte die mit dem Strahl kollidieren werden gesammelt und später zur Auswertung zurückgegeben. Statt eines Strahls kann auch eine Kegel oder ein anderes beliebiges Objekt benutzt werden. Die Kollisionsabfrage kann entweder direkt über eine Kollision mit einzelnen Polygonen der Geometrie eines Objektes oder über eine Kollision mit einem Bound Objekt erfolgen. Die Art der zurückgelieferten Ergebnisse des Picking können sein: – das der ViewingPlatform am dichtesten liegende Objekt direkt – der komplette Pfad des Objektes im Szenengraph – eine Liste von allen Objekten, die durch den Pickray geschnitten werden Abb. 10 verdeutlicht das Prinzip des Picking. 26 Objekte im VirtuaUniverse Pickray Mauscursor Position Betrachter Bildschirm/Canvas3D sichtbarer Bereich des VirtualUniverse Abb. 10 Prinzip des Picking In Java3D wird das Picking über ein PickCanvas Objekt realisiert. An dessen Konstruktor werden die Zeichenfläche (Canvas3D) und die BranchGroup , in der sich die Objekte befinden, welche ausgewählt werden können, übergeben . Mit der Methode setShapeLocation (MouseEvent mevent) wird dem PickCanvas mitgeteilt, an welcher Stelle das MausEvent seinen Ursprung auf der Zeichenfläche hat. Von dieser Position aus wird vom PickCanvas ein Pickray in die Szene ausgestrahlt und liefert das dichteste Ergebnis über die Methode pickClosets() in Form eines PickInfo Objektes zurück. Das PickInfo Objekt beinhaltet jetzt eine Referenz auf ein Objekt aus der BranchGroup des oben übergebenen Szenezweiges. Das ausgewählte Objekt, in diesem Fall vom Typ Shape3D, enthält keine Informationen, ob es an einem B3D_node oder B3D_edge Knoten hängt. Aus diesem Grund erfolgt mit der Methode getParent() eine rekursive Abfrage der Elternknoten des Objektes bis in die Ebene, in der die Objekte vom Typ B3D_node bzw. B3D_edge in den Szenengraph eingehängt wurden. Sobald die zugehörige Klasse des ausgewählten Objektes feststeht, wird das label des jeweiligen übergeordneten Knoten- bzw. Kanten-Objektes ausgelesen und an die Referenz eines Text3D Objekt übergeben. Dieses referenziert ein Text3D Objekt, welches in der Klasse B3D_graph direkt an die ViewingPlatform gehängt wurde, damit es sich mit ihr bewegt. 2.4 Grafische Ausgabe B3D_viewer Die Klasse B3D_viewer ist die Hauptlkasse des B3D Programms und ist eine Erweiterung der Klasse JFrame. In dieser Klasse werden alle nötigen Objekte erzeugt. Dazu gehören sämtliche Objekte die zur grafischen Ausgabe wie z.B. der Canvas3D sowie das komplette SimpleUniverse, das einzige statische B3D_graph Objekt sowie sämtliche benutzten Behavior Objekte und Dialoge. Zu den Dialogen gehört ein JfileChooser, der zur Auswahl der DOT-Datei dient. Sobald dieser Dialog gestartet wird, wird entweder das B3D_graph Objekt mit dem vom JFileChooser zurückgegebem Dateinamen/-Pfad instanziert bzw. an die ReInit() Methode des B3D_graph Objektes übergeben. Während des Aufbaus eines neuen Graphen in dem B3D_graph Objekt wird dieses solange aus dem Szenengraph ausgehängt und danach wieder eingefügt. 27 2.5 zukünftige Erweiterungen 2.5.1 Scheibengraph- und Kugelgraphmodell Eine weitere Darstellungsmöglichkeit zu dem unter 2.2 vorgestellten Kegelgraphmodell wäre der scheibenförmige Graph oder auch Scheibengraph. Der root-Knoten liegt als einziger Knoten außerhalb der Scheibe und wird direkt über dem Zentrum platziert. Der direkte Nachfolger des root-Knotens liegt im Mittelpunkt der Scheibe. Die übrigen Knoten werden auf konzentrischen Ringen um diesen Mittelpunkt angeordnet, deren Radien proportional zu den vorkommenden distance Werten der Knoten sind, d.h. ein Knoten mit dem distance Wert k würde sich auf dem k. Ring, vom Zentrum aus gesehen, mit dem ungefähren Radius k*R befinden. Der Freiraum eines Ringes, d.h. die Differenz der Radien des nächstkleineren und nächstgrößeren Ringes, ist proportional zu der Anzahl der Knoten, die auf dem Ring liegen. Je mehr Knoten sich auf einem Ring befinden, desto mehr ausgehende Kanten können auch vorhanden sein. Durch mehr Freiraum zwischen den Ringen wird die Anzahl von Überschneidungen ein wenig verringert. Knoten, die Ziel gleichartiger Kanten sind und sich auf dem gleichem Kreis befinden, werden wie in Abschnitt 2.1 beschrieben gruppiert, d.h jeder Operation des formalen Modells wird ein Kreissektor zugewiesen. Auf den Schnittlinien dieser Kreissektoren und der konzentrisch angeordneten Kreise werden die Zielknoten einer Operation positioniert. Die Position der Knoten auf diesen Kreissegmenten kann anhand von weiteren Eigenschaften wie z.B. der ID oder der Anzahl der ausgehenden Kanten erfolgen. Damit ein Mindestabstand der Knoten auf einem Kreisabschnitt gewährleistet werden kann, kann entweder der Radius des jeweiligen Kreises vergrößert werden oder die Öffnungswinkel der Kreissektoren der Operationen werden mit der Anzahl der Zielknoten der entsprechenden Operation gewichtet. Die Art der Kanten werden wie beim Kegelgraphen eingeteilt: 1. Kanten, die Knoten unterschiedlicher Ringe verbinden, werden durch Splines dargestellt, deren mittlere Stützpunkte außerhalb der Scheibe liegen. Da hier zwei Ausrichtungen, über- und unterhalb der Scheibe, zu Verfügung stehen, kann die Anzahl der Überschneidungen durch geschickte Wahl verringert werden. 2. Kanten, die Knoten des selben Ringes verbinden, werden durch Geraden (Sekanten) dargestellt, solange die Verbindung nicht den nächstkleineren Kreis schneidet. Falls das der Fall sein sollte, muss diese Verbindung ebenfalls durch einen Spline, der auf, über- oder unterhalb der Scheibe liegt, dargestellt werden. 3. Kanten deren Ziel- und Ausgangsknoten identisch sind, werden wie beim Kegelgraphmodell dargestellt. siehe 2.2. Ein Vorteil des Scheibengraphen ist, dass sich sämtliche Knoten, die das Ziel gleichartiger Operation sind, räumlich gesehen, dicht beieinander befinden. Außerdem hat die Anzahl der Kanten keine direkte Auswirkung auf die Ausdehnung des Graphen auf der X-Z-Ebene, da diese durch den Radius des äußersten Ringes bestimmt wird. Nachteilig wirkt sich aus, dass für die Positionierung der Knoten nur zwei Dimensionen genutzt werden können. Somit ist die Raumausnutzung relativ schlecht. Dafür steht allerdings mehr Raum für die Kanten zur Verfügung, wodurch Überschneidungen von Kanten und Knoten völlig vermieden und Überschneidungen zwischen Kanten verringert werden können. Abb. 11 zeigt einen schematischen Scheibengraph mit zwei Ebenen und vier verschiedenen Operationen(a,b,c und d) 28 b Kreissektor d Ebene 2 b Ebene 1 a Kreissektor a d b c Kreissektor c Kreissektor b Abb. 11 Schematischer Scheibengraph mit zwei Ebenen und vier verschiedenen Operationen (a,b,c und d) Der kugelförmige Graph oder auch Kugelgraph ist eine Erweiterung des scheibenförmigen Graphen auf die 3. Dimension. Dabei werden die Knoten entsprechend ihres distance Wertes auf konzentrischen Kugelschalen deren Mittelpunkt der erste Knoten nach dem root-Knoten bildet, verteilt. Der Raum wird in kegelförmige Kugelsektoren, ausgehend vom Zentrum, eingeteilt. Dabei entspricht jeder Kugelsektor wie beim Scheibengraphen einer Operation des formalen Modells. Auf den Schnittlinien der Kugelsektoren und der Kugelschalen werden die Zielknoten gleichartiger Kanten positioniert. Die Kanten des Kugelgraphen werden ähnlich wie die des Scheibengraphen dargestellt, nur dass alle Kanten innerhalb des Graphen verlaufen. Der Vorteil dieser Darstellung ist, dass nun mehr Raum zur Positionierung der Knoten zur Verfügung steht. D.h. im Gegensatz zum Scheibengraphen können pro distance Ebene mehr Knoten verteilt werden, ohne dass der Radius der Kugelschale erhöht werden muss, um einen Mindestabstand der Knoten zueinander zu gewährleisten. Wie auch beim Scheibengraphen wird die maximale Ausdehnung des gesamten Graphen nicht direkt durch die Anzahl der Knoten oder Kanten sondern durch die Anzahl der distance Ebenen bestimmt. Jetzt müssen sich Knoten und Kanten allerdings den gleichen Raum teilen. Das kann zu mehr Überschneidungen führen als beim Scheibengraphen. Abb. 12 zeigt einen schematischen Kugelgraph mit zwei Ebenen/Kugelschalen und zwei verschiedenen Operationen (a und b) 29 Kugelsektor c c c Ebene 2 Ebene 1 b a a Kugelsektor a a a b Kugelsektor b b Abb. 12 Schematischer Kugelgraph mit zwei Ebenen/Kugelschalen und zwei verschiedenen Operationen (a und b) Für die hier verwendeten Zustandsgraphen ist das kugelförmige Graphenmodell anscheinend die übersichtlichste aller hier vorgestellten Darstellungsformen. 2.5.2 Performance Verbesserungen Ein wichtiger Aspekt der Performance in bewegten Szenen mit vielen einzelnen Objekten ist die Anzahl der TransformGroup Objekte und deren Relationen zu einander. Wie in 1.4.6 schon erwähnt sind die Transformationen mehrerer aufeinander folgender TransformGroup Knoten kumulativ. Hintereinander ausgeführte Transformationen innerhalb einer BranchGroup können zwar durch die compile() Methode einer übergeordneten BranchGroup zusammengefasst werden, allerdings nicht, wenn eine oder mehrere Transformationen auf einem Pfad mehr als einen Untergraphen besitzen, siehe Abb. 13. Sobald sich dann durch ein Behavior oder ein sonstiges Ereignis, die Position eines Objektes verändert, müssen sämtliche Transformationen auf dem Pfad von der Wurzel bis zum Blatt nacheinander abgearbeitet werden. Dabei wird die Matrix, welche die absolute Bewegung des Objekts beschreibt, mit der Matrix des Transform3D Objektes der ersten TransformGroup multipliziert, das Ergebnis mit der nächsten usw. bis zur letzten TransformGroup auf dem Pfad. Die resultierende Matrix beschreibt dann die relative Bewegung des Objektes. 30 BG BG BG BG TG TG TG TG compile TG compile S TG TG TG S S S TG S S Abb. 13 Varianten der Transformationen mehrerer aufeinander folgender TransformGroup Knoten innerhalb einer BranchGroup Die Matrixmultiplikationen mit der allgemein bekannten Methode des Ausmultiplizierens der einzelnen Spalten- und Zeilenvektoren hat einen Aufwand von O(n3) n steht für die Anzahl der Elemente einer quadratischen Matrix und ist in diesem Fall 4, da das Transform3D Objekt jeder TransformGroup intern mit einer 4x4 Matrix arbeitet. Bei m TransformGroup Knoten auf dem Pfad von der Wurzel bis zum Objekt entspricht das dem Aufwand von O(m*n3). Bei ungünstiger Anwendung von TransformGroup Objekten, d.h. r Pfade mit jeweils m TransformGroup Objekten, wäre der Aufwand O(r*m*n3). Gerade bei zeitkritischen Anwendungen ist dieser Aufwand zu hoch. Bei Animationen, z.B. bei denen diese Anzahl an Matrixmultiplikationen mehrere Male pro Sekunde ausgeführt werden, ist es schwer, so eine flüssige Bewegung darstellen zu können. Es gibt Verfahren um den Aufwand der Matrixmultiplikation zu senken, z.B. das Verfahren von Schönhagen und Strassen[7], welches einen Aufwand von “nur“ O(n2.81) hat. Leider geht aus der Java3D Dokumentation nicht hervor, welches Verfahren zur Martixmultiplikation eingesetzt wird. Der erste einfache Ansatz den Aufwand zu verringern, wäre die Matrix eines Transform3D Objektes während der Entwicklung nur auf benötigte Transformationen, also Translationen, Rotationen, Skalierungen oder Scherbewegungen, zu beschränken. Diese Beschränkung erfolgt durch das Setzen von bestimmten flags des Transform3D Objektes. Wird z.B. ein Transform3D Objekt nur für Translationen verwendet, wird anstelle der ganzen Matrix nur der letzte Spaltenvektor, der die relative Translation des Transform3D Objektes beschreibt, mit der absoluten Bewegungsmatrix multipliziert. Dies entspricht dann nur noch dem Aufwand der Multiplikation einer Matrix mit einem Vektor. 31 Ein weiterer Schritt zur Vereinfachung wäre, nur eine TransformGroup zu benutzen, welche die absolute Position eines Objektes beschreibt. Damit wäre der Aufwand O(1*n2) bzw. O(1*n3) jeweils für auf Translationen beschränkte bzw. nicht beschränkte Transform3D Objekte. Die nächste Verbesserungsmöglichkeit wäre, die TransformGroup Knoten ganz wegfallen zu lassen und die Objekte von vorn herein absolut im Raum zu platzieren. Der Vorteil dieser Methode ist, dass die Matrix, welche die absolute Bewegung des Objektes beschreibt, nur mit dem schon absoluten Ortsvektor des Objekts multipliziert wird. Bei Objekten Typ Shape3D wird dies ermöglicht, indem die in ihnen enthaltenen Geometrie Objekte, z.B. LineArray oder IndexedTriangleArray, absolut im Raum platziert werden. Für Objekte vom Typ Primitive wie Sphere oder Cone ist diese Möglichkeit in Java3D nicht vorgesehen. Solche Objekte lassen sich nur mit Hilfe einer TransformGroup im Raum verschieben. Mit ein wenig mehr Programmieraufwand können diese Objekte aber mit Hilfe von Geometrieobjekten nachgebildet werden und somit auch absolut im Raum platziert werden. Die absolut platzierten Objekte können dann direkt an die BranchGroup Wurzel eines Szenezweiges gehängt werden. Wenn eine Szene statisch ist und es nur darum geht, diese Szene von allen Seite zu betrachten, empfiehlt es sich, anstelle der Szene die “virtuelle Kamera“ zu bewegen. Der Effekt für den Betrachter bleibt der Gleiche, nur dass jetzt die Transformation statt auf alle Ortsvektoren der Objekte der Szene nur auf den Ortsvektor der “virtuellen Kamera“ angewendet werden braucht. Hierbei muss beachtet werden, dass die Transformation, die auf die “virtuellen Kamera“ angewendet wird, invertiert werden muss. (siehe Beispiel 1.4.9) 32 2.6 B3D Beispiele Beispiel Phonebook.mch: MACHINE phonebook SETS Name; Code VARIABLES db DEFINITIONS scope_Name == 1..8; scope_Code == 3..8; GOAL == (card(db)=3) INVARIANT db : Name +-> Code INITIALISATION db := {} OPERATIONS add(nn,cc) = PRE nn:Name & cc:Code & nn /: dom(db) THEN db := db \/ { nn |-> cc} END; cc <-- lookup(nn) = PRE nn : dom(db) & nn:Name THEN cc:=db(nn) END; update(nn,cc) = PRE nn:Name & cc:Code & nn : dom(db) THEN db(nn) := cc END; reset = PRE db: Name --> Code THEN db := {} END END verschiedene Kanten: 98 Kanten insgesamt: 3673 Knoten insgesamt: 2692 Arbeitsspeicherverbrauch: ca. 1150MB! 33 Beispiel lift.mch: MACHINE Lift ABSTRACT_VARIABLES floor INVARIANT floor : 0..99 /* NAT */ INITIALISATION floor := 4 OPERATIONS inc = PRE floor<99 THEN floor := floor + 1 END ; dec = BEGIN floor := floor - 1 END END verschiedene Kanten: 3 Kanten insgesamt: 8 Knoten insgesamt: 6 Arbeitsspeicherverbrauch: ca. 70MB Fazit: Pro Knoten und Kante werden ca. 10KB Speicher verbraucht. Dieser hohe Speicherverbrauch ist auf die Text3D Objekte der Knoten und Kanten zurückzuführen, welche allgemein aus einer hohe Anzahl an Polygonen bestehen. 34 Quellenverzeichnis: [1] http://www.graphviz.org/.......................................... ......................................................7 [2] https://javacc.dev.java.net/servlets/ProjectDocumentList? folderID=110&expandFolder=110&folderID=109........................................................8 [3] http://de.wikipedia.org/wiki/VRML............................................................................... 9 [4] http://en.wikipedia.org/wiki/Tessellation ......................................................................... http://www-lehre.inf.uos.de/cg2/material/20021218/Tesselation.htm..........................15 [5] http://de.wikipedia.org/wiki/Extrusion_(Geometrie).................................................... 15 [6] http://de.wikipedia.org/wiki/Rot-Schwarz-Baum..........................................................23 [7] http://www-i1.informatik.rwth-aachen.de/Lehre/SS05/PSAuD/Handout_Grams.pdf http://www.heptargon.de/algorithm/ead/node37.html .................................................30 allgemeine Quellen: - Java3d 1.5.0 Documentation http://download.java.net/media/java3d/javadoc/1.5.0/index.html – Java 1.5.0 Documentation http://java.sun.com/j2se/1.5.0/docs/api/index.html Hilfsmittel: für dieses Dokument: -OpenOffice 2.1 -Gimp 2.2 für das Programm B3D: -Java JDK 1.5.0_09 -Java3D 1.5.0 -Eclipse SDK 3.2.1 -JavaCC Plugin für Eclipse Erstellung der DOT-Dateien: -ProB 1.2.4 35 Abbildungsverzeichnis: Abb.1 Beispiel DOT-Datei und Zustandsgraph ........................................................................8 Abb.2 Java3D Klassenhierarchie............................................................................................... 9 Abb.3 einfacher Szenengraph.................................................................................................. 10 Abb.4 Verwendung mehrerer Locale Objekte ........................................................................ 12 Abb.5 Auswirkungen der Reihenfolge von Bewegungsoperationen ...................................... 13 Abb.6 Graphische Ausgabe und Szenengraph des Beispielprogramms „Hallo-Welt“............16 Abb.7 Schematischer Aufbau des Programms B3D................................................................ 17 Abb.8 Schematischer Aufbau eines Kegelgraphen .................................................................20 Abb.9 Darstellung der Geraden- und Splinekanten ................................................................ 22 Abb.10 Prinzip des Picking........................................................................................................26 Abb.11 Schematischer Scheibengraph ...................................................................................... 28 Abb.12 Schematischer Kugelgraph ........................................................................................... 29 Abb.13 Zusammenfassen mehrerer Transformationen .............................................................30