3-D Visualisierung von Zustandsräumen formaler Modelle

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