Transformationen in Java 3D Eine Ausarbeitung von Florian Hüter im Rahmen des Seminars Java3D von Prof. Dr. W. Heinzel an der FH Fulda im WS 2002/03. Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 Inhaltsverzeichnis: Seite 1. Allgemeines zu Java3D ................................................................3 1.1. Was ist Java3D? ...................................................................3 1.2. Der Szenengraph..................................................................4 2. Theoretische Grundlagen von Transformationen ......................5 3. Transformationen in Java3D ........................................................7 3.1. Allgemeines..........................................................................7 3.2. Vorbereitungen ....................................................................8 3.3. Translation..........................................................................13 3.4. Skalierung...........................................................................15 3.5. Rotation ..............................................................................17 3.6. Anmerkungen.....................................................................22 4. Ein praktisches Beispiel..............................................................23 Quellenverzeichnis ......................................................................26 Anhang .........................................................................................27 Seite 2 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 1. Allgemeines zu Java3D 1.1. Was ist Java3D? Java3D ist eine Bibliothek von Java-Klassen zur Darstellung dreidimensionaler Grafik innerhalb von Java-Applikationen und Applets. Die Struktur einer darzustellenden Szene wird hierbei durch den Szenengraphen definiert, der den kompletten Aufbau einer 3DSzene in einer baumartigen Struktur beinhaltet. Dadurch erlaubt Java3D eine relativ komfortable Handhabung von Objekten und Unterobjekten. Für die Darstellung von Szenen setzt Java3D auf ein lokal vorhandenes Rendering- API wie z.B. OpenGL oder unter Windows auch DirectX auf. Dadurch kann Java3D auch die für das jeweilige Rendering-API optimierte Hardware zur Szene ndarstellung nutzen. Der Nachteil hierbei ergibt sich jedoch dadurch, dass durch die Verwendung der lokalen Rendering- API zwangsläufig eine Plattformabhängigkeit entsteht. Allerdings muss lediglich lokal eine entsprechend angepasste Version von Java3D einmalig installiert werden. Die kompilierten Java-Programme an sich laufen durch die Verwendung der immer gleichen Schnittstelle zur Anwendung bzw. zum Applet dann wiederum auf jeder Plattform, auf der Java3D installiert ist. Für die Darstellung einer Szene analysiert Java3D den Szenengraphen und konvertiert diesen in eine auf Renderingperformance optimierte Form, die von der jeweils vorhandenen Rendering- API abhängig ist. Bei Veränderungen der Szene erfolgt wenn zulässig eine erneute Optimierung. Der Programmierer muss sich also nicht um die Optimierung einer Szene in Bezug auf Renderingperformance kümmern – dies nimmt Java3D ihm ab. Seite 3 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 1.2. Der Szenengraph Der Szenengraph von Java3D besteht aus zwei Teilen: Zum einen dem „Viewing“- Zweig, der alle für die Darstellung erforderlichen Parameter enthält und zum anderen dem „Content“-Zweig, der den Inhalt der Szene (Geometrie, Transformationen, etc.) enthält. Der „Viewing“- Zweig wird an dieser Stelle nicht näher erläutert, da sowohl dieser als auch das Locale- VirtualUniverse-Objekt und das virtual universe vollständig automatisch erzeugt werden können, locale indem die Klasse SimpleUniverse verwendet wird. Augenmerk soll an dieser Stelle BranchGroup BranchGroup jedoch kurz auf den „Content“Zweig gerichtet werden, da sich TransformGroup … hieran sehr schön der strukturierte Aufbau eines Java3D-Szenengraphen … verdeutlichen lässt. An der Spitze des content „Content “- Zweiges stehen zunächst ein oder viewing Abbildung 1 – Der Szenengraph von Java3D mehrere BranchGroup-Objekte. An diese lassen sich nun beliebig viele weitere BrachGroup- und/oder TransformGroup-Objekte anhängen. Wie später noch ersichtlich wird lassen sich so komplexe Strukturen erzeugen, die jedoch stets übersichtlich und verständlich sind. Mit folgendem Code-Fragment lassen sich beispielsweise ein Ober- und ein Unterarm auf einfachste Weise modellieren: BranchGroup oberarm = new BranchGroup(); oberarm.addChild(new ColorCube(0.3)); BranchGroup unterarm = new BranchGroup(); oberarm.addChild(new ColorCube(0.3)); oberarm.addChild(unterarm); // // // // // erzeugt BranchGroup oberarm erstellt Würfel als Oberarm erzeugt BranchGroup unterarm erstellt Würfel als Unterarm ordnet Unterarm Oberarm unter Diese logische Struktur eines Arms kann jetzt durch Bewegung der BranchGroup „oberarm“ als Ganzes bewegt werden und darüber hinaus lässt sich der Unterarm durch Transformationen der BranchGroup „unterarm“ auch einzeln bewegen. Der Unterarm muss nun noch an die richtige Stelle im Koordinatensystem geschoben werden, und ein zugegebenermaßen sehr einfacher Arm ist fertig. Auf Translation und andere Transformationen sowie die Erstellung eines komplexe n, funktionsfähigen Armes wird jedoch erst später eingegangen. Seite 4 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 2. Theoretische Grundlagen von Transformationen Die theoretische Grundlage für die hier behandelten Transformationen Translation, Skalierung und Rotation bilden mathematische Matrizenmultiplikationen. So lassen sich n-dimensionale Transformationen als Matrizen der Größe n+1 x n+1 darstellen. Die hier behandelten Transformationen im dreidimensionalen Raum können also als 4x4-Matrizen beschrieben werden. An dieser Stelle soll nur kurz auf die mathematischen Hintergründe der Transformationen eingegangen werden, da das Hauptaugenmerk dieser Ausarbeitung auf der Anwendung von Transformationen in Java3D liegt. Nähere Informationen zu diesem Thema finden sich für Interessierte jedoch im Skript von Prof. Dr. W. Heinzel zur Lehrveranstaltung „Graphische Datenverarbeitung“ an der FH Fulda im Sommersemester 2000 im dritten Kapitel „Transformationen“ auf den Seiten 62-73. Jedoch sind die dortigen Ausführungen auf OpenGL bezogen, deren Koordinatensystem eine von Java3D abweichende Achsenbezeichnung verwendet. Für geometrische Operationen in Java3D gilt folgendes Koordinatensystem: y x z Abbildung 2 – Das Koordinatensystem von Java3D Die einfachste Transformation, zumindest in Bezug auf die dafür notwendige mathematische Berechnung stellt die Translation dar. Diese lässt sich durch folgende Matrix abbilden, wobei der Punkt P mit den Koordinaten (u, v, w) um den Vektor (a b c) verschoben werden soll: [uvw1] 1 0 0 0 0 1 0 0 0 0 1 0 a b c 1 = [ u+a v+b w+c 1 ] Seite 5 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 Auch die Skalierung lässt sich durch eine einfache Matrizenmultiplikation erreichen. Hierzu ist folgende Rechenoperation für jeden Eckpunkt P (u, v, w) eines zu skalierenden Objekts durchzuführen, wenn mit dem Vektor (a b c) skaliert werden soll: [uvw1] a 0 0 0 0 b 0 0 0 0 c 0 0 0 0 1 = [ au bv cw 1 ] Hierbei ist jedoch zu beachten, dass der Mittelpunkt des Objekt zunächst in den Ursprung des Koordinatensystems geschoben und nach der Skalierung wieder zurück bewegt werden muss, um den Mittelpunkt des Objekts durch die Skalierung nicht zu verschieben. Die aufwändigste der hier behandelten Transformationen stellt die Rotation dar. Jedoch soll auch hier nur sehr kurz aus Gründen der Vollständigkeit eine der benötigten Matrizen vorgestellt und keine detaillierten Erklärungen dazu gegeben werden. Eine Rotation eines Punktes P mit den Koordinaten (u, v, w) mit dem Winkel a um die x-Achse lässt sich durch folgende Matrizengleichung darstellen: [uvw1] 1 0 0 0 0 cos(a) -sin(a) 0 0 sin(a) cos(a) 0 0 0 0 1 = [ u v*cos(a)- z*sin(a) y*sin(a)+z*cos(a) 1 ] Alle anderen möglichen Rotationen werden durch ähnliche Gleichungen abgebildet, auf die hier jedoch nicht näher eingegangen werden soll. Die Drehrichtung für die Rotationen um die jeweiligen Achsen lässt sich übrigens aus Abbildung 2 auf der vorangegangenen Seite entnehmen, die das Koordinatensystem von Java3D mit den Rotations richtungen für alle Achsen zeigt. Seite 6 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 3. Transformationen in Java3D 3.1. Allgemeines Standardmäßig werden in Java3D alle Objekte bezüglich der Weltkoordinaten platziert. Diese umständliche und unpraktikable Vorgehensweise lässt sich allerdings durch die Verwendung von Transformationsgruppen, so genannte TransformGroup-Objekte umgehen. Ein TransformGroup-Objekt erlaubt die Verwendung eines neuen Koordinatensystems, das relativ zum Koordinatensystem des Vaters angeordnet ist. Verändert (transformiert) man nun die TransformGroup, bewegen sich gleichzeitig auch alle Objekte bzw. Modelle in dieser Gruppe. Nun lässt sich dieses Vater-Kind-Prinzip in Java3D natürlich über beliebig viele Ebenen durchführen. Hierbei akkumulieren sich selbstverständlich die Transformationen der einzelnen Stufen. Darüber hinaus sollte man wissen, dass der Szenengraph von unten nach oben abgearbeitet wird. Tiefer im Szenengraph angeordnete Transformationen werden also zuerst ausgeführt. Allerdings lassen sich durch die Verwendung von TransformGroup-Objekten noch keine Transformationen ausführen. Dazu benötigt man wenigstens ein Transform3D-Objekt. Dieses dient zur Beschreibung der Transformationen einer TransformGroup und muss dieser entsprechend zugewiesen werden. Auf die einzelnen Befehle, die dazu in Java3D erforderlich sind, wird in den folgenden Abschnitten detailliert anhand von Beispielen eingegangen. „Content“-Zweig des Szenengraphen BranchGroup TransformGroup … … TransformGroup TransformGroup TransformGroup Transform3D … rotate scale Abbildung 3 – Der „Content“-Zweig des Szenengraphen Seite 7 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 3.2. Vorbereitungen An dieser Stelle beginnen wir nun mit der eigentlichen Programmierung. Doch zuerst sollten wir eine Umgebung erzeugen, die für alle späteren Demonstrationen und Erklärungen verwendet werden kann. Zunächst benötigen wir einige allgemeine Klassen, die wir importieren müssen. Dies geschieht durch folgende Codezeilen: // Zu importierende Klassen import java.applet.Applet; import java.awt.*; import com.sun.j3d.utils.applet.MainFrame; import com.sun.j3d.utils.universe.*; import com.sun.j3d.utils.geometry.ColorCube; import javax.media.j3d.*; import javax.vecmath.*; Danach beschreiben wir zunächst einmal den Rumpf der zu erstellenden Klasse SimpleScene, den wir später nach und nach füllen werden. Es werden folgende Funktionen für unser Java3D-Programm benötigt: public class SimpleScene extends Applet { // Im Konstruktor wird lediglich eine Java3D-Szene vorbereitet und // anschließend dargestellt. Daher kann dieser Konstruktor später universal // verwendet werden. Die eigentliche Szene wird in der Funktion // createSceneGraph() weiter unten erstellt. public SimpleScene() { } // Hier wird die eigentliche Szene erstellt (bzw. modelliert) public BranchGroup createSceneGraph() { } // Die Hauptfunktion, die die Szene als Anwendung oder Applet aufruft public static void main(String[] argv) { new MainFrame(new SimpleScene(), 256, 256); } } Wie bereits die Kommentare im Quelltext verdeutlichen, wird hier die Programm-Klasse SimpleScene erstellt, die einen Konstruktor besitzt, der die Szene vorbereitet und lädt. Außerdem existiert eine Funktion createSceneGraph, in der später die komplette Szene erstellt wird. Am Ende der Klasse steht noch die java-typische main-Funktion, die das Programm startet, indem sie ein Objekt der Klasse SimpleScene erzeugt. Da jetzt das ungefähre Aussehen einer Java3D-Anwendung von der Struktur her klar sein sollte, wollen wir nun damit beginnen, die einzelnen Funktionen zu füllen. Seite 8 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 Fangen wir also mit der Erstellung des Konstruktors an. Dieser muss ein SimpleUniverseObjekt inklusive Virtual-Universe-Objekt, Locale-Objekt und dem kompletten „Viewing“-Zweig erzeugen und den Szenengraphen dort hinein laden. Für nähere Informationen hierzu siehe Kapitel 1.2 „Der Szenengraph“. Um kurz die wichtigsten Funktionen näher zu beschreiben sei gesagt, dass zunächst der „Content“-Zweig des Szenengraphen über den Aufruf der später noch detailliert beschriebenen Funktion createSceneGraph() erzeugt wird. Dieser wird anschließend aus Performancegründen kompiliert und optimiert. Danach wird ein Canvas3D-Objekt erzeugt und positioniert, das einer Leinwand entspricht und für die Darstellung der Szene benötigt wird. Nun wird unter Verwendung dieses Canvas3D-Objekts ein SimpleUniverse erzeugt, dass die Basis einer jeden Java3D-Szene bildet. Danach wird die Kamera noch so positioniert, dass der im Fenster sichtbare Bereich der Szene genau von -1 bis 1 sowohl auf der X- als auch auf der Y-Achse reicht. Zum Schluss muss nur noch der bereits kompilierte „Content“-Zweig des Szenengraphen in dieses SimpleUniverse geladen werden und fertig ist die Java3D-Umgebung. Der Java-Code hierzu sieht folgendermaßen aus: public SimpleScene() { // Setzen des Layout-Managers setLayout(new BorderLayout()); // Aufruf der Funktion zur Erstellung des Szenengraphen (bzw. der Szene) BranchGroup scene = createSceneGraph(); // Kompiliert (Optimiert) den Szenengraph scene.compile(); GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration(); // Erzeugt eine Leinwand (=Canvas), auf der die Szene dargestellt wird Canvas3D canvas3D = new Canvas3D(config); // In den Mittleren Bereich der Szene (Center) das Canvas3D-Objekt einfügen add("Center", canvas3D); // Erstellung des SimpleUniverse als Root-Objekt der gesamten Szene SimpleUniverse universe = new SimpleUniverse(canvas3D); // Die Kameraposition wird entlang der Z-Achse so versetzt, dass die // Fenstergrenzen jeweils bei -1 und 1 des Koordinatensystems liegen universe.getViewingPlatform().setNominalViewingTransform(); // Szenengraph in SimpleUniverse einfügen universe.addBranchGraph(scene); } Jetzt wäre das Programm ja beinahe schon lauffähig, jedoch fehlt noch das Wichtigste: Die eigentliche Szene. Um nun also etwas darzustellen benötigen wir noch etwas Inhalt in Seite 9 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 der Funktion createSceneGraph(). Diese werden wir zunächst erst einmal mit einem bunten Würfel füllen, um die Vorbereitungen für die folgenden Kapitel abzuschließen. Im Einzelnen sieht dies nun so aus: Zunächst einmal erstellen wir das Root-Objekt oder auch die Wurzel der Szene, dem alle anderen Objekte untergeordnet werden. Dieses nennen wir auch dementsprechend rootObject. Um die Szene später noch transformieren zu können, benötigen wir nun ein TransformGroup-Objekt, welches den Namen tGroup erhält. Mit dessen Hilfe können wir später sämtliche Transformationen auf alle Objekte als Ganzes ausführen, die dieser TransformGroup angehören. Um nun endlich ein sichtbares Objekt in die Szene zu bringen, verwenden wir die importierte Klasse ColorCube. Diese erzeugt einen Würfel, dessen Seiten unterschiedlich eingefärbt und alle den angegebenen Wert vom Mittelpunkt entfernt sind. Diesen Würfel ordnen wir auch sogleich unserer TransformGroup unter, um ihn später transformieren zu können. Des Weiteren muss die TransformGroup noch unserem rootObject als Kind zugewiesen werden, um den Baum zu komplettieren. Am Ende geben wir dann noch eine Referenz auf unser rootObject zurück. Der hierzu erforderliche Code sieht folgendermaßen aus: // Hier wird die eigentliche Szene erstellt (bzw. modelliert) public BranchGroup createSceneGraph() { // Root-Objekt innerhalb der Szene wird erstellt BranchGroup rootObject = new BranchGroup(); // Eine TransformGroup erzeugen, um später Transformationen durchzuführen TransformGroup tGroup = new TransformGroup(); // Den ColorCube als Kind-Objekt in die TransformGroup aufnehmen tGroup.addChild(new ColorCube(0.5)); // Die TransformGroup als Kind-Objekt ins Root-Objekt aufnehmen rootObject.addChild(tGroup); // Die Szene bzw. das Root-Objekt zurückgeben return rootObject; } Und nun zum Abschluss dieser Vorbereitung noch einmal den Code im Ganzen, der dazu benutzt werden kann, eine eigene Java3D-Szene zu erschaffen und der auch im Folgenden als Grundlage für weitere Erklärungen verwendet wird: // Zu importierende Klassen import java.applet.Applet; import java.awt.*; import com.sun.j3d.utils.applet.MainFrame; import com.sun.j3d.utils.universe.*; import com.sun.j3d.utils.geometry.ColorCube; import javax.media.j3d.*; import javax.vecmath.*; Seite 10 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 public class Translation extends Applet { // Der Translation-Konstruktor kann für jedes beliebige andere Programm // ebenfalls als Konstruktor verwendet werden, da er eine Szene in Java3D // komplett vorbereitet und darstellt. // Die eigentliche Szene wird in der Funktion createSceneGraph() weiter // unten erstellt. public Translation() { // Setzen des Layout-Managers setLayout(new BorderLayout()); // Aufruf der Funktion zur Erstellung des Szenengraphen (bzw. der Szene) BranchGroup scene = createSceneGraph(); // Kompiliert (Optimiert) den Szenengraph scene.compile(); GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration(); // Erzeugt eine Leinwand (=Canvas), auf der die Szene dargestellt wird Canvas3D canvas3D = new Canvas3D(config); // In den Mittleren Bereich der Szene (Center) das Canvas3D-Objekt einfügen add("Center", canvas3D); // Erstellung des SimpleUniverse als Root-Objekt der gesamten Szene SimpleUniverse universe = new SimpleUniverse(canvas3D); // Die Kameraposition wird entlang der Z-Achse so versetzt, dass die // Fenstergrenzen jeweils bei -1 und 1 des Koordinatensystems liegen universe.getViewingPlatform().setNominalViewingTransform(); // Szenengraph in SimpleUniverse einfügen universe.addBranchGraph(scene); } // Hier wird die eigentliche Szene erstellt (bzw. modelliert) public BranchGroup createSceneGraph() { // Root-Objekt innerhalb der Szene wird erstellt BranchGroup rootObject = new BranchGroup(); // Transform3D-Objekt erzeugen, das die Rotationsmatrix aufnimmt sowie mit // Einheitsmatrix belegen Transform3D translate = new Transform3D(); // Um den angegebenen Vektor verschieben (X,Y,Z) translate.set(new Vector3d( 0.0, 0.0, 0.0 )); // Eine TransformGroup erzeugen, die das Transform3D-Objekt aufnimmt TransformGroup tGroup = new TransformGroup(); // Die Transformation der TransformGroup zuordnen tGroup.setTransform(translate); // Den ColorCube als Kind-Objekt in die TransformGroup aufnehmen tGroup.addChild(new ColorCube(0.5)); // Die TransformGroup als Kind-Objekt ins Root-Objekt aufnehmen rootObject.addChild(tGroup); // Die Szene bzw. das Root-Objekt zurückgeben return rootObject; } // Die Hauptfunktion, die die Szene als Anwendung oder Applet aufruft public static void main(String[] argv) { new MainFrame(new Translation(), 256, 256); } } Seite 11 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 Als Ergebnis sollte dieser Code dann ein Ergebnis liefern, dass als Anwendung ausgeführt dieses Fenster darstellt: Abbildung 4 - SimpleScene Man sieht einen Würfel mit einem Meter, denn genau das ist die Standard-Maßeinheit des Koordinatensystems von Java3D, Kantenlänge, der zentriert im Fenster angezeigt wird. Die Zentrierung entsteht dadurch, dass die Mitte des Fensters den Ursprung des Koordinatensystems bildet und der Mittelpunkt des Würfels genau auf diesem liegt. Um dies zu verändern, benötigen wir als erste Transformation die Translation, die im nächsten Kapitel erklärt wird. Seite 12 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 3.3. Translation Kommen wir nun also zur wahrscheinlich einfachsten aller Transformationen – der Translation oder Verschiebung. In Java3D gibt es eigentlich nur zwei Möglichkeiten, eine solche Translation durchzuführen. Entweder man verwendet hierzu einen dreidimensionalen Verschiebungsvektor (das ist die einfachere und gebräuchlichere Methode), oder man gibt eine 4x4-Matrix an, die die Verschiebung enthält (das ist die komplizierte in Kapitel 2 angesprochene Methode). Beginnen wir also zunächst mit der Verschiebung unseres Würfels aus 3.2. per Verschiebungsvektor. Hierzu benötigen wir als Erstes ein neues Objekt der Klasse Transform3D, das die Verschiebung oder auch allgemein die Transformation aufnimmt, die auf ein Objekt angewendet werden soll. Diesem Transform3D-Objekt geben wir anschließend einen Vektor an, um den verschoben werden soll. In unserem Beispiel ist dies der Vektor (-1.0, 1.0, -2.0), der das Koordinatensystem einer TransformGroup um einen Meter nach links (-1.0), einen Meter nach oben (1.0) und zwei Meter nach hinten, also ins Bild hinein (-2.0) verschiebt. Um dies zu erreichen muss die Transformation nun nur noch mit der Methode setTransform dem TransformGroup-Objekt zugewiesen werden, welches transformiert werden soll. Da wir momentan erst ein solches Objekt erstellt haben, fällt die Auswahl nicht schwer. Der Code hierzu stellt sich folgendermaßen dar (rot markierte Textelemente sind neu hinzugekommen): public BranchGroup createSceneGraph() { // Root-Objekt innerhalb der Szene wird erstellt BranchGroup rootObject = new BranchGroup(); // Eine TransformGroup erzeugen, um später Transformationen durchzuführen TransformGroup tGroup = new TransformGroup(); // Transform3D-Objekt erzeugen, das die Transformationen aufnimmt Transform3D translate = new Transform3D(); // Um den angegebenen Vektor verschieben (X,Y,Z) translate.set(new Vector3d( -1.0, 1.0, -2.0 )); // Die Transformation der TransformGroup zuordnen tGroup.setTransform(translate); // Den ColorCube als Kind-Objekt in die TransformGroup aufnehmen tGroup.addChild(new ColorCube(0.5)); // Die TransformGroup als Kind-Objekt ins Root-Objekt aufnehmen rootObject.addChild(tGroup); // Die Szene bzw. das Root-Objekt zurückgeben return rootObject; } Seite 13 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 Das Ergebnis dieser Transformation sieht dann folgendermaßen aus, wobei links noch einmal die Ausgangssituation ohne Transformation und rechts die Szene nach erfolgter Translation abgebildet ist. Nun kann man auch erstmals erkennen, dass es sich bei unserem ColorCube wirklich um einen Würfel und nicht nur um ein Quadrat handelt. Abbildung 5 – SimpleScene Abbildung 6 - Translation Kommen wir nun zur etwas unübersichtlicheren Methode, der Translation mittels Vorgabe der Transformationsmatrix. Hierbei gilt es, den gewünschten Verschiebungsvektor in die in Kapitel 2 für die Translation angegebene 4x4-Matrix einzusetzen und diesen dann wie oben beschrieben der TransformGroup zuzuweisen. Für obige Translation (-1.0, 1.0, -2.0) ergibt sich also folgendes Bild: Allgemeine Translationsmatrix für eine Verschiebung um (a, b, c): 1 0 0 0 0 1 0 0 0 0 1 0 a b c 1 Einsetzen des Vektors (-1.0, 1.0, -2.0) ergibt folgende Matrix: 1 0 0 0 0 1 0 0 0 0 1 0 -1 1 -2 1 Wir müssen also, um dieselbe Verschiebung wie oben zu erreichen mit obiger Matrix transformieren. Dies geschieht durch folgende Code-Zeile, mit der der entsprechende translate.set-Methodenaufruf aus obigem Programm ersetzt wird: // Folgende Matrix als Transformationsmatrix angeben translate.set(new Matrix4d( 1.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, -2.0, 0.0, 0.0, 0.0, 1.0 )); Mit Hilfe von Matrizen lassen sich übrigens auch alle anderen hier angesprochenen Transformationen ausführen. Jedoch wird darauf nicht mehr explizit eingegangen. Seite 14 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 3.4. Skalierung Als nächste ebenfalls sehr einfache Transformation wollen wir nun die Skalierung, also das Stauchen und Strecken einzelner oder auch aller Koordinatenachsen behandeln. Für die Skalierung bietet Java3D zwei Methoden an. Bei einer werden alle Achsen des Koordinatensystems mit dem gleichen Faktor skaliert, bei der anderen lässt sich für jede Achse der gewünschte Skalierungsfaktor separat einstellen. Beginnen wir mit der uniformen Skalierung aller Achsen. Hier kann wieder auf unsere SimpleScene zurückgegriffen werden, deren Funktion createSceneGraph nur noch um die Skalierung des Koordinatensystems ergänzt werden muss. Dies geschieht nach folgender Vorgehensweise, die der bei der Translatio n erklärten gleicht. Wir erzeugen ein Transform3D-Objekt, das die Transformation aufnehmen kann, setzen den Skalierungsfaktor fest und weisen das Transform3D am Ende der TransformGroup zu. Der benötigte Java-Code sieht folgendermaßen aus, wobei rote Codefragmente zur Ausgangsszene SimpleScene hinzugefügt wurden: // Hier wird die eigentliche Szene erstellt (bzw. modelliert) public BranchGroup createSceneGraph() { // Root-Objekt innerhalb der Szene wird erstellt BranchGroup rootObject = new BranchGroup(); // Eine TransformGroup erzeugen, um später Transformationen durchzuführen TransformGroup tGroup = new TransformGroup(); // Transform3D-Objekt erzeugen, das die Transformationen aufnimmt Transform3D scale = new Transform3D(); // Alle Achsen um den angegebenen Vektor skalieren scale.set(0.5); // Die Transformation der TransformGroup zuordnen tGroup.setTransform(scale); // Den ColorCube als Kind-Objekt in die TransformGroup aufnehmen tGroup.addChild(new ColorCube(0.5)); // Die TransformGroup als Kind-Objekt ins Root-Objekt aufnehmen rootObject.addChild(tGroup); // Die Szene bzw. das Root-Objekt zurückgeben return rootObject; } Jedoch lassen sich wie bereits erwähnt die einzelnen Koordinatenachsen auch unterschiedlich skalieren. Dies erreicht man durch Verwendung der Methode setScale anstatt der universalen set-Methode. Der setScale-Methode muss man hierbei lediglich einen dreidimensionalen Vektor übergeben, der sich aus Skalierung in X-, Y- und ZRichtung zusammensetzt. Seite 15 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 Für eine unterschiedliche Skalierung der Achsen ist also die Zeile „scale.set…“ durch folgende zu ersetzen, wenn die X-Achse mit dem Faktor 0,5 gestaucht, die Y-Achse mit dem Faktor 1,5 gestreckt und die Z-Achse unverändert bleiben soll: // Die Achsen mit dem angegebenen Vektor skalieren (X, Y, Z) scale.setScale(new Vector3d( 0.5, 1.5, 0.0 )); Zum Vergleich hier noch einmal die SimpleScene mit dem Standard ColorCube der Kantenlänge 1 (Abbildung 7), der uniform mit dem Faktor 0,5 gestauchte ColorCube (Abbildung 8) und der mittels Skalierungsvektor veränderte ColorCube (Abbildung 9). Abbildung 7 - ColorCube ohne Transformation Abbildung 8 – Uniforme Skalierung Seite 16 von 32 Abbildung 9 – Skalierung per Vektor Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 3.5. Rotation Kommen wir nun zur letzten hier behandelten Transformation – der Rotation oder Drehung um eine oder mehrere Achsen. Diese bietet die größte Vielfalt an Methoden und möglichen Vorgehensweisen. Fangen wir jedoch erst einmal ganz einfach an – mit der Rotation um eine Koordinatenachse, beispielsweise die x-Achse. Hierzu benötigen wir wiederum die Szene SimpleScene, die wie oben um ein Transform3D-Objekt erweitert wird. Diesem Transform3D-Objekt weisen wir nun als Transformation eine Rotation um die x-Achse zu, indem wir dessen Methode rotX verwenden. Entsprechend existieren die Methoden rotY und rotZ. Diese Methoden verlangen nur einen Übergabeparameter, und zwar den Wert, um den rotiert werden soll. Hierbei ist zu beachten, dass die Werte im Radiusmaß übergeben werden müssen. Die Konstante Pi entspricht hierbei 180°, 2 mal Pi wären dann entsprechend 360°. Da wir den Würfel aber zunächst nur um 45° rotieren wollen, übergeben wir der Methode rotX Pi/4. Abschließend müssen TransformGroup wir zuweisen. das Der Transform3D-Objekt hierzu wie erforderliche gewohnt noch Programm-Code der sieht folgendermaßen aus: // Hier wird die eigentliche Szene erstellt (bzw. modelliert) public BranchGroup createSceneGraph() { // Root-Objekt innerhalb der Szene wird erstellt BranchGroup rootObject = new BranchGroup(); // Eine TransformGroup erzeugen, um später Transformationen durchzuführen TransformGroup tGroup = new TransformGroup(); // Transform3D-Objekt erzeugen, das die Transformationen aufnimmt Transform3D rotate = new Transform3D(); // Um 45° um die X-Achse rotieren (PI = 180°) rotate.rotX(Math.PI/4d); // Die Transformation der TransformGroup zuordnen tGroup.setTransform(rotate); // Den ColorCube als Kind-Objekt in die TransformGroup aufnehmen tGroup.addChild(new ColorCube(0.5)); // Die TransformGroup als Kind-Objekt ins Root-Objekt aufnehmen rootObject.addChild(tGroup); // Die Szene bzw. das Root-Objekt zurückgeben return rootObject; } Das Ergebnis dieser Rotation im Vergleich zur ursprünglichen SimpleScene ist auf der Folgeseite abgebildet. Seite 17 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 Abbildung 10 – SimpleScene Seite 18 von 32 Abbildung 11 - Rotation um die x-Achse Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 Natürlich lassen sich mit Java3D auch mehrere Rotationen verketten. Dazu erstellen wir zunächst zwei weitere Transform3D-Objekte und belegen eines mit einer Rotation um die yAchse, das andere mit einer Rotation um die z-Achse. Anschließend multiplizieren wir die beiden Rotationsmatrizen nacheinander jeweils mit der Ausgangstransformation und schon haben wir eine verkettete Rotation um alle drei Achsen. Der Quellcode hierzu sieht folgendermaßen aus: // Hier wird die eigentliche Szene erstellt (bzw. modelliert) public BranchGroup createSceneGraph() { // Root-Objekt innerhalb der Szene wird erstellt BranchGroup rootObject = new BranchGroup(); // Eine TransformGroup erzeugen, um später Transformationen durchzuführen TransformGroup tGroup = new TransformGroup(); // Transform3D-Objekte für die X-, Y- und Z-Rotation erzeugen Transform3D rotate = new Transform3D(); Transform3D rotateY = new Transform3D(); Transform3D rotateZ = new Transform3D(); // Um 45° um die X-Achse rotieren (PI = 180°) rotate.rotX(Math.PI/4d); // Um 45° um die Y-Achse rotieren rotateY.rotY(Math.PI/4d); // Um 45° um die Z-Achse rotieren rotateZ.rotZ(Math.PI/4d); // Rotationsmatrizen nacheinander multiplizieren rotate.mul(rotateY); rotate.mul(rotateZ); // Die Transformation der TransformGroup zuordnen tGroup.setTransform(rotate); // Den ColorCube als Kind-Objekt in die TransformGroup aufnehmen tGroup.addChild(new ColorCube(0.5)); // Die TransformGroup als Kind-Objekt ins Root-Objekt aufnehmen rootObject.addChild(tGroup); // Die Szene bzw. das Root-Objekt zurückgeben return rootObject; } Hierbei ist jedoch zu beachten, dass die Rotationen nacheinander durchgeführt werden. Das komplette Koordinatensystem wird also zunächst um 45° um die x-Achse rotiert, anschließend um 45° um die bereits veränderte y-Achse und abschließend um die zweimal gedrehte z-Achse. Eine Slideshow dieser ganzen Rotationskette findet sich zum besseren Verständnis auf der folgenden Seite. Hierbei sollte man immer das Java3D-typische Koordinatensystem (s. Abbildung 2) im Hinterkopf haben. Seite 19 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 Abbildung 12 – Ausgangsszene Abbildung 13 - Nach der x-Rotation Abbildung 14 - Nach der y-Rotation Abbildung 15 - Nach der z-Rotation Wie man jedoch schnell erkennt, ist das ein ziemlich umständlicher Weg, mehrere Rotationen durchzuführen. Natürlich gibt es dafür eine einfachere Methode, die zudem den Vorteil hat, dass nicht nacheinander rotiert wird, sondern alle Rotationen quasi gleichzeitig durchgeführt werden. Jede Einzelrotation wird also direkt am Ausgangskoordinatensystem vorgenommen und das Ergebnis ist daher auch viel einfacher vorherzusagen. Benutzt wird hierzu lediglich eine neue Methode namens SetEuler, der man drei Rotations faktoren für die drei möglichen Rotationsrichtungen übergibt. Der Quellcode und das entsprechende Ergebnis stehen auf der Folgeseite. Man beachte den Unterschied der Ausgabe zum vorangegangenen Beispiel. Seite 20 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 // Hier wird die eigentliche Szene erstellt (bzw. modelliert) public BranchGroup createSceneGraph() { // Root-Objekt innerhalb der Szene wird erstellt BranchGroup rootObject = new BranchGroup(); // Eine TransformGroup erzeugen, um später Transformationen durchzuführen TransformGroup tGroup = new TransformGroup(); // Transform3D-Objekt erzeugen, das die Transformationen aufnimmt Transform3D rotate = new Transform3D(); // In jede Richtung jeweils um den angegebenen Winkel rotieren rotate.setEuler(new Vector3d( Math.PI/4d, Math.PI/4d, Math.PI/4d )); // Die Transformation der TransformGroup zuordnen tGroup.setTransform(rotate); // Den ColorCube als Kind-Objekt in die TransformGroup aufnehmen tGroup.addChild(new ColorCube(0.5)); // Die TransformGroup als Kind-Objekt ins Root-Objekt aufnehmen rootObject.addChild(tGroup); // Die Szene bzw. das Root-Objekt zurückgeben return rootObject; } Das entsprechende Ergebnis dieser Operation sieht dann so aus (im Gegensatz zur verketteten Multiplikation weiter oben): Abbildung 16 - Rotation mittels setEuler Seite 21 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 3.6. Anmerkungen Grundsätzlich lassen sich durch Matrizenmultiplikation alle möglichen Transformationen miteinander kombinieren, jedoch sollte man wie bereits angesprochen aber auf die Reihenfolge der einzelnen Transformationen achten. Wie gesagt wird immer zuerst die erste Transformation ausgeführt und das Koordinatensystem dieser TransformGroup entsprechend verändert. Die nächste Transformation ganz gleich welcher Art derselben TransformGroup bezieht sich dann mit ihren Angaben auf dieses veränderte Koordinatensystem. Des Weiteren sollte der Vollständigkeit halber an dieser Stelle noch erwähnt werden, dass sich das Koordinatensystem eines Transform3D-Objekts wieder in den Anfangszustand versetzen lässt, die Transformationen dieses Objekts also rückgängig gemacht werden, wenn man die Methode setIdentity() des entsprechenden Transform3D-Objekts anwendet. Im nächsten abschließenden Kapitel besprechen wir nun noch den Aufbau einer etwas komplexeren Szene in Java3D. Viel Spaß damit! Seite 22 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 4. Ein praktisches Beispiel Beginnen wir nun also mit der Anwendung des Gelernten. Ziel unseres Beispiels ist die sehr vereinfachte Nachbildung eines menschlichen Körperrumpfes und eines Armes. Der Arm soll sich per Tastatureingabe bewegen lassen. Hierbei sollen Ober- und Unterarm getrennt voneinander steuerbar sein. Beginnen wir also mit der Planung des Modells: Zunächst erschaffen wir ein zentrales Objekt, das die übrigen Objekte aufnimmt. Dieses nennen wir rootObject. Anschließend erstellen wir eine TransformGroup koerper für den Körper, in die wir eine Box als Symbol für den Rumpf aufnehmen. Ihr ordnen wir eine TransformGroup kopf unter, in die wir eine Kugel zur Darstellung des Kopfes aufnehmen. Nun muss der Kopf noch auf dem Körper platziert werden. Dies ge schieht durch Translation der TransformGroup kopf an die entsprechende Stelle. Als nächstes generieren wir eine TransformGroup oberarmPositionierung, die dazu dient, den Oberarm später an die richtige Stelle in der Szene bewegen zu können. Diese ordnen wir der TransformGroup koerper unter. Anschließend erstellen wir eine weitere TransformGroup oberarmRotater und ordnen diese der Gruppe oberarmPositionierung unter. Mit Hilfe von oberarmRotater können wir später den Arm bewegen bzw. rotieren. Die TransformGroup oberarmRotater muss außerdem für Veränderungen während der Laufzeit freigegeben werden. Dies geschieht durch Aufrufen der Methode setCapability mit dem Parameter TransformGroup.ALLOW_TRANSFORM_WRITE. Jetzt erstellen wir noch eine TransformGroup oberarm und ordnen diese der Gruppe oberarmRotater unter. Nun platzieren wir noch einen Zylinder in der Gruppe oberarm, der den Oberarm darstellt. Zum Schluss verschieben wir diesen so, dass der Punkt, an dem der Oberarm mit dem Körper verbunden ist, im Ursprung des übergeordneten Koordinatensystems der Gruppe oberarmRotater liegt und schon ist der Oberarm fertig. Um den Oberarm später jedoch per Tastatur bewegen zu können, müssen wir der TransformGroup oberarmRotater noch ein Verhalten zuweisen. Dies wird hier jedoch nicht näher erläutert, da dies nicht zum Thema Transformationen gehört und den Umfang dieser Ausarbeitung sprengen würde. Für nähere Informationen hierzu siehe QuelltextKommentare im Anhang bzw. die Ausarbeitung eines Kommilitoenen zum Thema Interaktionen. Seite 23 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 Als letztes Objekt wollen wir nun noch einen Unterarm erstellen, der ebenfalls bewegt werden kann. Die Vorgehensweise hierbei ist analog zur oben beschriebenen Erstellung des Oberarms und wird daher nicht näher erläutert. Die fertige Szene hat also folgende Struktur: BG rootObject Box Sphere TG koerper TG oberarmPositionierung TG kopf TG oberarmRotater Cylinder TG oberarm TG unterarmPositionierung TG unterarmRotater Cylinder BG: BranchGroup TG unterarm TG: TransformGroup Abbildung 17 - Der "Content"-Zweig des Szenengraphen Den einzelnen Objekten der Szene lassen sich nun noch unterschiedliche Farben zuordnen, um sie besser unterscheiden zu können. Außerdem sollte die TransformGroup koerper mit allen ihr untergeordneten Objekten noch etwas verschoben und rotiert werden, um den Oberarm besser in Szene zu setzen. Wie das fertige Programm aussehen könnte, ist auf der nächsten Seite zu sehen. Sowohl Ober- als auch Unterarm lassen sich in jeweils zwei Richtungen per Tastatureingabe bewegen. Der Quelltext dafür kann im Anhang dieser Ausarbeitung eingesehen werden. Seite 24 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 Abbildung 18 - Das fertige Programm Dieses Beispielprogramm stellt selbstverständlich nur einen Ansatz für den Einstieg in die Programmierung mit Java3D dar und kann bei Bedarf noch beliebig erweitert werden. So könnte man das Modell vervollständigen bzw. feiner modellieren. Auch die Verwendung von Farben und Texturen kann hieran gut erprobt werden. Java3D bietet zur weiteren Gestaltung eine große Vielfalt an Möglichkeiten, die es zu erlernen gilt. Ich hoffe, diese Ausarbeitung konnte die in sie gesetzten Erwartungen erfüllen und wünsche weiterhin noch viel Spaß mit Java3D. Seite 25 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 Quellenverzeichnis [HalM] Multimediaprogrammierung und Sensorik 5 – Java3D Teil 5 Autor Michael Haller (01.10.2002) http://webster.fhs-hagenberg.ac.at/staff/haller/mmp5_20012002/06java3d_1.pdf abgerufen am 26.10.2002 [HeiW] Skript zur Lehrveranstaltung Graphische Datenverarbeitung an der FH Fulda Autor Prof. Dr. W. Heinzel (Stand: SS 2000) [JAPI] Java3D 1.3 – API http://java.sun.com [JoyK] Online Computer Graphics Notes – Transformation Autor Ken Joy (06.12.1999) http://graphics.cs.ucdavis.edu/GraphicsNotes/Transformations/Transformations.html abgerufen am 21.10.2002 [SoNa] Siggraph 99 - Introduction to Programming with Java 3D Autoren Henry Sowizral & Dave Nadeau (Juli 1999) http://www.sdsc.edu/~nadeau/Courses/Siggraph99/ abgerufen am 15.10.2002 Seite 26 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 Anhang Hier nun der Quellcode, des in Kapitel 4 besprochenen Beispielprogramms: /* Autor: Florian Hüter letzte Änderung: 26.10.2002 * * Klassenname: Mensch * * Entwickelt im Rahmen des Seminars Java3D bei Prof. Dr. W. Heinzel an der * FH Fulda im WS 2002/03. * * Es wird ein Teil eines Menschen nachgebildet, dessen Ober- und Unterarm * separat voneinander bewegt werden kann. * * Tastenbelegung: * --------------* * Oberarmsteuerung: Unterarmsteuerung: * Anlegen: d Anwinkeln: k * Strecken: a Strecken: i * Nach vorne: s Nach außen: l * Nach hinten: w Nach innen: j * Zurücksetzen: f Zurücksetzen: h * * * Anmerkung: * Da Teile der Klasse SimpleBehavior einem Beispielprogramm von Sun * entnommen wurden, muss folgender Lizenzvermerk im Quellcode erscheinen: * * * @(#)SimpleBehaviorApp.java 1.1 00/09/22 16:24 * * Copyright (c) 1996-2000 Sun Microsystems, Inc. All Rights Reserved. * * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use, * modify and redistribute this software in source and binary code form, * provided that i) this copyright notice and license appear on all copies of * the software; and ii) Licensee does not utilize the software in a manner * which is disparaging to Sun. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. * * This software is not designed or intended for use in on-line control of * aircraft, air traffic, aircraft navigation or aircraft communications; or in * the design, construction, operation or maintenance of any nuclear * facility. Licensee represents and warrants that it will not use or * redistribute the Software for such purposes. */ /*** import import import import Zu importierende Klassen ***/ java.applet.Applet; java.awt.*; com.sun.j3d.utils.applet.MainFrame; com.sun.j3d.utils.universe.*; Seite 27 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 import import import import import com.sun.j3d.utils.geometry.*; javax.media.j3d.*; javax.vecmath.*; java.awt.event.*; java.util.Enumeration; public class Mensch extends Applet { /*** Konstruktor ***/ // Der Konstruktor initalisiert die Java3D-Umgebung public Mensch() { setLayout(new BorderLayout()); // Aufruf der Funktion zur Erstellung des Szenengraphen (bzw. der Szene) BranchGroup scene = createSceneGraph(); // Kompiliert (Optimiert) den Szenengraph scene.compile(); GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration(); // Erzeugt eine Leinwand (=Canvas), auf der die Szene dargestellt wird Canvas3D canvas3D = new Canvas3D(config); // Java3D-Koordinatensystem wird zur Leinwand hinzugefügt add("Center", canvas3D); // Erstellung des SimpleUniverse als Root-Objekt der gesamten Szene SimpleUniverse universe = new SimpleUniverse(canvas3D); // Die Kameraposition wird entlang der Z-Achse so versetzt, dass die // Fenstergrenzen jeweils bei -1 und 1 des Koordinatensystems liegen universe.getViewingPlatform().setNominalViewingTransform(); // Szenengraph in SimpleUniverse einfügen universe.addBranchGraph(scene); } /*** Verhaltensklasse für Oberarm ***/ // Klasse, die das Verhalten des Oberarms steuert // Sie wurde einem Beispiel von Sun entnommen (s.o.) und erweitert public class SimpleBehavior extends Behavior { private private private private TransformGroup oberarm; Transform3D oberarmRotation = new Transform3D(); Transform3D oberarmRotationTemp = new Transform3D(); WakeupCondition cond; // Konstruktor SimpleBehavior(TransformGroup tg1){ this.oberarm = tg1; } // Initalisiere das Verhalten public void initialize(){ cond = new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED); this.wakeupOn(cond); } // Verhalten // Wird von Java automatisch augerufen, wenn entsprechender "Stimulus" // kommt (bestimmtes Event eintritt) public void processStimulus(Enumeration criteria){ oberarmRotationTemp.set(oberarmRotation); WakeupOnAWTEvent event = (WakeupOnAWTEvent)criteria.nextElement(); Seite 28 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 KeyEvent key = (KeyEvent)event.getAWTEvent()[0]; char c = key.getKeyChar(); // Per Tasteneingabe Richtung der Drehung steuern switch(c) { case 'd': oberarmRotation.rotZ(0.1); oberarmRotation.mul(oberarmRotationTemp); oberarm.setTransform(oberarmRotation); break; case 'a': oberarmRotation.rotZ(-0.1); oberarmRotation.mul(oberarmRotationTemp); oberarm.setTransform(oberarmRotation); break; case 'w': oberarmRotation.rotX(0.1); oberarmRotation.mul(oberarmRotationTemp); oberarm.setTransform(oberarmRotation); break; case 's': oberarmRotation.rotX(-0.1); oberarmRotation.mul(oberarmRotationTemp); oberarm.setTransform(oberarmRotation); break; // Anfangszustand wiederherstellen case 'f': oberarmRotation.setIdentity(); oberarm.setTransform(oberarmRotation); break; default: {} } this.wakeupOn(cond); } } // Ende der Klasse SimpleBehavior /*** Verhaltensklasse für Unterarm ***/ // Klasse, die das Verhalten des Unterarms steuert // SimpleBehavior2 stellt nahezu die gleiche Funktion wie SimpleBehavior // zur Verfügung, jedoch werden hier andere Tasten verwendet public class SimpleBehavior2 extends Behavior { private private private private TransformGroup unterarm; Transform3D unterarmRotation = new Transform3D(); Transform3D unterarmRotationTemp = new Transform3D(); WakeupCondition cond; // Konstruktor SimpleBehavior2(TransformGroup tg1){ this.unterarm = tg1; } // Initalisiere das Verhalten public void initialize(){ cond = new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED); this.wakeupOn(cond); } // Verhalten // Wird von Java automatisch augerufen, wenn entsprechender "Stimulus" // kommt (bestimmtes Event eintritt) public void processStimulus(Enumeration criteria){ unterarmRotationTemp.set(unterarmRotation); WakeupOnAWTEvent event = (WakeupOnAWTEvent)criteria.nextElement(); KeyEvent key = (KeyEvent)event.getAWTEvent()[0]; char c = key.getKeyChar(); // Per Tasteneingabe Richtung der Drehung steuern switch(c) { Seite 29 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 case 'j': unterarmRotation.rotZ(0.1); unterarmRotation.mul(unterarmRotationTemp); unterarm.setTransform(unterarmRotation); break; case 'l': unterarmRotation.rotZ(-0.1); unterarmRotation.mul(unterarmRotationTemp); unterarm.setTransform(unterarmRotation); break; case 'i': unterarmRotation.rotX(0.1); unterarmRotation.mul(unterarmRotationTemp); unterarm.setTransform(unterarmRotation); break; case 'k': unterarmRotation.rotX(-0.1); unterarmRotation.mul(unterarmRotationTemp); unterarm.setTransform(unterarmRotation); break; // Anfangszustand wiederherstellen case 'h': unterarmRotation.setIdentity(); unterarm.setTransform(unterarmRotation); break; default: {} } this.wakeupOn(cond); } } // Ende der Klasse SimpleBehavior2 /*** Farbgenerierungsmethode ***/ public Appearance makeAppearance( double r, double g, double b) { Appearance app = new Appearance(); ColoringAttributes farbe = new ColoringAttributes(); farbe.setColor((float) r, (float) g, (float) b); app.setColoringAttributes(farbe); return app; } /*** Modellierung der Szene ***/ public BranchGroup createSceneGraph() { // Festlegung einiger Farben Appearance appRot = makeAppearance(1.0, 0.0, 0.0); Appearance appGruen = makeAppearance(0.0, 1.0, 0.0); Appearance appBlau = makeAppearance(0.0, 0.0, 1.0); Appearance appGelb = makeAppearance(1.0, 1.0, 0.0); // Root-Objekt innerhalb der Szene wird erstellt BranchGroup rootObject = new BranchGroup(); /*** Körper ***/ // Erzeugt den Körper als oberste TransformGroup TransformGroup koerper = new TransformGroup(); koerper.addChild(new Box(0.25f, 0.4f, 0.1f, appGelb)); // Den Körper durch Verschiebung und Drehung besser in Szene setzen // Alle untergeordneten Objekte werden dadurch mit verschoben Transform3D koerperAusrichtung = new Transform3D(); koerperAusrichtung.set(new Vector3d(0.4, 0.0, 0.0)); Transform3D koerperDrehung = new Transform3D(); Seite 30 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 koerperDrehung.rotY(0.3); koerperAusrichtung.mul(koerperDrehung); koerper.setTransform(koerperAusrichtung); // Den Körper dem Wurzelobjekt der Szene als Kind zuweisen rootObject.addChild(koerper); /*** Kopf ***/ TransformGroup kopf = new TransformGroup(); kopf.addChild(new Sphere(0.15f, appRot)); // Positioniert den Kopf am Körper und ordnet den Kopf dem Körper unter Transform3D kopfPositionierung = new Transform3D(); kopfPositionierung.set(new Vector3d(0.0, 0.55, 0.0)); kopf.setTransform(kopfPositionierung); koerper.addChild(kopf); /*** Oberarm ***/ // Erzeugt die TransformGroup oberarmPositionierung, die zur Positionierung // des Oberarms am Körper verwendet wird TransformGroup oberarmPositionierung = new TransformGroup(); Transform3D oberarmAnKoerper = new Transform3D(); Transform3D oberarmDrehung = new Transform3D(); oberarmAnKoerper.set(new Vector3d(-0.25, 0.4, 0.0)); oberarmDrehung.rotZ(-Math.PI/4d); oberarmAnKoerper.mul(oberarmDrehung); oberarmPositionierung.setTransform(oberarmAnKoerper); koerper.addChild(oberarmPositionierung); // Erzeugt die TransformGroup oberarmRotater, die zur Rotation des // Oberarms verwendet wird und oberarmPositionierung untergeordnet ist TransformGroup oberarmRotater = new TransformGroup(); oberarmPositionierung.addChild(oberarmRotater); // Erlaubt das Verändern dieser TransformGroup während der Laufzeit oberarmRotater.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); // Die TransformGroup oberarm enthält den eigentlichen Oberarm TransformGroup oberarm = new TransformGroup(); oberarmRotater.addChild(oberarm); oberarm.addChild(new Cylinder(0.06f, 0.25f, appBlau)); Transform3D oberarmZuUrsprung = new Transform3D(); // Positioniert den Oberarm mit dem Drehpunkt im Zentrum des übergeordneten // Koordinatensystems oberarmZuUrsprung.set(new Vector3d(0.0, -0.125, 0.0)); oberarm.setTransform(oberarmZuUrsprung); // Verhalten zur Interaktion hinzufügen // Rotiert wird hierbei die TransformGroup oberarmRotater SimpleBehavior oberarmRotationBehavior = new SimpleBehavior(oberarmRotater); oberarmRotationBehavior.setSchedulingBounds(new BoundingSphere()); oberarmPositionierung.addChild(oberarmRotationBehavior); /*** Unterarm ***/ // Erzeugt die TransformGroup unterarmRotater, die zur Positionierung // des Unterarms am Oberarm verwendet wird TransformGroup unterarmPositionierung = new TransformGroup(); Transform3D unterarmAnKoerper = new Transform3D(); Transform3D unterarmDrehung = new Transform3D(); unterarmAnKoerper.set(new Vector3d(0.0, -0.15, 0.0)); unterarmPositionierung.setTransform(unterarmAnKoerper); oberarm.addChild(unterarmPositionierung); // Erzeugt die TransformGroup unterarmRotater, die zur Rotation des // Unterarms verwendet wird und unterarmPositionierung untergeordnet ist Seite 31 von 32 Ausarbeitung zum Thema „Transformationen in Java3D“ von Florian Hüter im Oktober 2002 TransformGroup unterarmRotater = new TransformGroup(); unterarmPositionierung.addChild(unterarmRotater); // Erlaubt das Verändern dieser TransformGroup während der Laufzeit unterarmRotater.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); // Die TransformGroup unterarm enthält den eigentlichen Unterarm TransformGroup unterarm = new TransformGroup(); unterarmRotater.addChild(unterarm); unterarm.addChild(new Cylinder(0.06f, 0.25f, appGruen)); Transform3D unterarmZuUrsprung = new Transform3D(); // Positioniert den Oberarm mit dem Drehpunkt im Zentrum des übergeordneten // Koordinatensystems unterarmZuUrsprung.set(new Vector3d(0.0, -0.125, 0.0)); unterarm.setTransform(unterarmZuUrsprung); // Verhalten zur Interaktion hinzufügen // Rotiert wird hierbei die TransformGroup unterarmRotater SimpleBehavior2 unterarmRotationBehavior = new SimpleBehavior2(unterarmRotater); unterarmRotationBehavior.setSchedulingBounds(new BoundingSphere()); unterarmPositionierung.addChild(unterarmRotationBehavior); // Die Szene bzw. das Root-Objekt zurückgeben return rootObject; } // Die Hauptfunktion, die die Szene als Anwendung oder Applet aufruft public static void main(String[] argv) { new MainFrame(new Mensch(), 256, 256); } } Seite 32 von 32