Auch hochfliegende Ideen lassen sich mit Coin und wenigen Zeilen C++ schnell umsetzen: Aus einfachen Objekten mit Texturen und komplexen VRML-Modellen entsteht eine animierte 3D-Welt.
3D-Objekte wirken erst mit einer Textur (Oberflächengestaltung) realistisch. Wer mit Coin und C++ arbeitet (siehe Kasten “Rückblick”), kann diesen Effekt sehr einfach nutzen. Die Objekte muss man dafür nicht mühsam von Hand programmieren: Open Inventor und damit auch Coin können VRML-1.0-Dateien einbinden, für die es ausgereifte grafische Modellierwerkzeuge gibt. Durch Animation haucht der Entwickler seiner Welt Leben ein.
Rückblick |
|
Der erste Teil dieser kleinen Coin-Serie[1] beschrieb, wie mit Hilfe von Coin und SoQt dreidimensionale Grafiken in C++ entstehen. Coin[2] ist ein freier Klon von Open Inventor (SGI[3] und TGS[4]), der auf OpenGL aufbaut. Die Beispiele in diesem und im vorangegangenen Artikel enthalten keinen Coin-spezifischen Code. Die Programme sollten daher mit jeder Open-Inventor-Version und jedem Klon laufen, mit Ausnahme der Bindung zum Fenstersystem: Hier kommt Qt statt Motif zum Einsatz. Ein Vorteil von Open Inventor gegenüber OpenGL ist, dass Ersterer 3D-Szenen durch einen Szenengraphen beschreibt. Der Szenengraph ist als Baumstruktur aufgebaut; jedes 3D-Element ist in einem eigenen Knoten enthalten. Wie und wo Coin einen Knoten anzeigt hängt von seiner Position im Graphen ab. So ist es möglich, eine Szene (oder Teile davon) zu speichern oder ihr Verhalten (Interaktion und Animation) zu programmieren. |
Bindeglied der verschiedenen Teile einer 3D-Welt ist der Szenengraph. Für ihn gilt “Teile und herrsche”: Eine komplexe Szene ist einfacher zu programmieren, wenn sie in kleinere Teile zerlegt ist. Beim Programmieren einer Szene ist es ratsam, zu Beginn eine Liste mit allen benötigten Objekten und deren Eigenschaften zu erzeugen. Für jede Geometrie ist dann ein Gruppenknoten anzulegen. In den fügt man zuerst die gewünschten Transformationen ein (etwa Rotation oder Skalierung), ergänzt die Materialeigenschaften (etwa die Farbe) und beschreibt erst dann die eigentliche Geometrie. Die Reihenfolge ist wichtig, weil Open Inventor zum Rendern (darstellen) auf OpenGL zurückgreift.
Beim Unterteilen der Szene sollte man darauf achten, dass sich möglichst viele Objekte mehrfach verwenden lassen. Das spart Code und ist übersichtlicher. Eine Möglichkeit ist es, Funktionen zu schreiben, die eigene kleine Szenengraphen oder Gruppen über einen Zeiger zurückgeben. Auf diese Weise lassen sich die Komponenten einfach in den Szenengraphen einhängen.
Beim Programmieren ist es üblich, wiederkehrende Teile des Codes in Bibliotheken auszulagern. Das trifft auch auf die Grafikprogrammierung zu: Für vorgefertigte Beschreibungen von wiederkehrenden, komplexeren Objekten lassen sich Dateien einsetzen. Die beiden gängigsten Varianten Dateien einzubinden, sind dreidimensionale Szenen, die durch eigene Szenengraphen beschrieben werden, sowie 2D-Bilder (in diesem Zusammenhang auch Texturen genannt). Letztere werden als Oberfläche auf Objekte abgebildet.
Texturen
Texturen sollen Objekte realistischer aussehen lassen. Open Inventor kennt zwei Varianten: Texturen können durch eine Matrix im Speicher beschrieben sein oder als zweidimensionale Bilder in Dateien vorliegen. Ersteres ist sehr aufwändig und in der Regel nicht nötig. Um eine Textur aus einer Datei zu laden, erzeugt das Programm zuerst ein Objekt der Klasse »SoTexture2«. Dieses Objekt muss dann den Namen der Bilddatei erfahren:
SoTexture2 *texture = new SoTexture2;
texture->filename.setValue("textur.rgb");
Das Texturobjekt muss im Szenengraphen vor dem Objekt eingefügt sein, auf das es abgebildet wird. Open Inventor kennt zwei Wege, Texturen auf Objekte zu legen: Er kann eine Textur einzeln (der Default-Fall) oder mehrfach auf eine Oberfläche zeichnen. Coin nutzt die »simage«-Bibliothek[5], um Texturen zu laden, und unterstützt damit folgende Formate: JPEG (».jpg«), GIF (».gif«), Targa (».tga«), PIC (».pic«), SGI-RGB (».rgb«, ».bw«) und XWD (».xwd«).
Listing 1 zeigt Code, der eine Kugel mit der Weltkarte überzieht. Der Code ist als Funktion implementiert, die einen Zeiger vom Typ »SoSeparator« zurückliefert. An diesem Gruppenknoten hängen eine Textur »SoTexture2« und eine Kugel »SoSphere«. Die Datei »weltkarte.rgb« steht auf[8] bereit; sie stammt ursprünglich aus den Beispielen des Inventor Mentor. Abbildung 1 zeigt das Resultat dieser Funktion, eingebunden in einen Szenengraphen und gerendert mit »SoQtExaminer«.
Listing 1: Erde zeichnen |
01 SoSeparator* zeichneErde()
02 {
03 // Objekte für Gruppenknoten und Textur
04 SoSeparator *erde = new SoSeparator;
05 SoTexture2 *textur_erde = new SoTexture2;
06
07 // Name der Texturdatei
08 textur_erde->filename = "weltkarte.rgb";
09
10 // Textur an den Gruppenknoten hängen
11 erde->addChild(textur_erde);
12
13 // Kugel an den Gruppenknoten hängen
14 // => Textur auf die Kugeloberfläche zeichnen
15 erde->addChild(new SoSphere);
16
17 return erde;
18 }
|

Abbildung 1: Die Erde in 3D. Für diese Grafik genügen eine Kugel und die passende Oberflächentextur.
Externe Geometrien hinzufügen
Wie erwähnt kann in einer Szene ein Objekt mehrfach, an verschiedenen Stellen, in unterschiedlichen Größen und Ausrichtungen erscheinen. Da Open Inventor die Form unabhängig von der Lage, der Orientierung und dem Aussehen (etwa der Farbe) in einem Objekt beschreibt, kann es der Programmierer beliebig oft verwenden. Wie das Stuhl-Beispiel im ersten Artikel[1] zeigte, sind diese Objekte nur einmal nötig.
Die Beschreibung von Geometrien erfordert gerade bei komplizierteren Formen viel Code. Um ein größeres Projekt übersichtlicher zu gestalten, bietet es sich an, diese komplexeren Objekte in Dateien auszulagern. Hierfür kennt Open Inventor ein eigenes Dateiformat, ähnlich VRML. Die Endung der Dateinamen ist ».iv«, der Inhalt kann entweder aus Ascii-Zeichen oder aus binären Daten bestehen. Für kleinere Szenen empfiehlt es sich, die Daten in (lesbarem) Ascii zu speichern. Für größere Files ist das kompakte Binärformat besser geeignet, da die Dateien kleiner sind und Coin sie schneller lädt.
Die in Listing 2 vorgestellte Funktion »ladeGeometrie()« gibt einen Zeiger auf den Szenengraphen zurück, der in einer Datei gespeichert ist. Die Funktion enthält mehrere Fehlerbehandlungen, um nur gültige Szenen zu laden.
Listing 2: Geometrie aus Datei laden |
01 SoSeparator* ladeGeometrie(const char *dateiname)
02 {
03 // Wurzel des Szenengraphen
04 SoSeparator *datei_szene = new SoSeparator;
05
06 // Handler für Open-Inventor-Datei
07 SoInput myScene;
08
09 // Szenendatei öffnen
10 if (!myScene.openFile(dateiname))
11 {
12 printf("Fehler beim Laden der Datei '%s'n", dateiname);
13 return NULL;
14 }
15
16 // Hat die Datei ein gültiges Format?
17 if (!myScene.isValidFile())
18 {
19 printf("Die Datei '%s' ist keine gültige Inventor-Datein", dateiname);
20 return NULL;
21 }
22
23 // Szene lesen und an den Gruppenknoten 'datei_szene' hängen
24 datei_szene = SoDB::readAll(&myScene);
25
26 if (datei_szene == NULL)
27 {
28 printf("Fehler beim Lesen der Datei '%s'n", dateiname);
29 myScene.closeFile();
30 return NULL;
31 }
32
33 // Datei schließen
34 myScene.closeFile();
35
36 return datei_szene;
37 }
|
Einige Beispiele für Open-Inventor-Dateien sind auf[8] zu finden. Dort liegt auch »boeing767.iv« mit der Geometrie eines Boeing-767-Jets. Abbildung 2 stellt die Szene im »SoQtExaminer« dar. Ein Blick in die Ascii-Textversion zeigt, wie aufwändig die Beschreibung dieser Szene ist.

Abbildung 2: Das Flugzeugmodell ist in einer Textdatei beschrieben, die von einem Coin-Programm geöffnet und in den Szenengraphen eingebunden wird.
In der Grafik-Programmierung ist es üblich, komplexe Objekte (etwa Gestalten und Fahrzeuge) in Modellierprogrammen zu entwickeln. Die Objekte exportiert der Designer in das jeweilige Dateiformat (Open Inventor ».iv« oder VRML ».wrl«). Diese Files lädt das Coin-Programm zur Laufzeit. Es gibt eine Menge Modellier-Tools, viele sind kostenlos für den privaten Gebrauch. Sie unterstützen meist mehrere Dateiformate.
VRML
Nicht alle Modellierprogramme können 3D-Objekte als Open-Inventor-Datei exportieren. Die meisten Tools kennen aber das weit verbreitete VRML-Format (Virtual Reality Modeling Language), das auch Open Inventor zugrunde liegt. Entsprechend einfach ist es, VRML-Dateien in Open-Inventor-Files zu konvertieren. Um zum Beispiel eine Ascii-formatierte VRML-1.0-Datei in das Open-Inventor-Format zu verwandeln, genügt es, die erste Zeile anzupassen:
#VRML V1.0 ascii
Für Open Inventor muss diese Zeile so aussehen:
#Inventor V2.0 ascii
Alle Bezeichnungen in VRML 1.0 und Open Inventor sind identisch. Neuere VRML-Versionen enthalten allerdings Objekte, die Open Inventor nicht kennt. Die geplante Coin-Version 2.0 wird aber auch neuere VRML-Versionen unterstützen. Falls eine Modellier-Software weder VRML noch Open Inventor unterstützt, helfen vielleicht 3D-Dateikonverter wie 3dc[6]. Unter[7] sind weitere Tools aufgeführt.
Animationen
Bis jetzt wurden nur statische Objekte betrachtet. Der Benutzer konnte zwar die Szenen von mehreren Seiten betrachten, weil die Programme dafür die Klasse »SoQtExaminerViewer« benutzten. Open Inventor bietet aber auch Klassen an, um Animationen in den Szenen einzufügen. Dazu sind zwei Schritte nötig: Zuerst muss der Programmierer ein Objekt anlegen, das die Veränderungen in der Animation auslöst (triggert). Dieses Objekt heißt Engine. Es gibt im Wesentlichen zwei Gruppen von Engines: Zähler und Rechner. Das Ausgangssignal der Engine wird im zweiten Schritt mit einer Eigenschaft eines Objekts im Szenengraphen verknüpft. Diese Eigenschaft passt sich dann laufend den Vorgaben der Engine an.
Um die Weltkugel von Abbildung 1 um ihre eigene Achse rotieren zu lassen, fügt man zunächst ein Objekt der Klasse »SoRotationXYZ« vor der Kugel in den Graphen ein. Die Klasse hat zwei Felder: Rotationsachse (axis) und Rotationswinkel (angle). Mit einer Engine verbunden erzeugt der Rotationswinkel die gewünschte Animation.
Listing 3 erzeugt eine Knotengruppe, die mit der Funktion »zeichneErde()« aus Listing 1 eine rotierende Erde darstellt. Eine Engine der Klasse »SoTimerCounter« erzeugt die verschiedenen Winkel. Allerdings müssen alle Winkel in Open Inventor als Radialzahlen (und damit als Fließkommawert) angegeben sein, »SoTimerCounter« liefert aber nur natürliche (ganzen) Zahlen. Das Umformen der Gradzahlen in Radialwerte gelingt mit »SoCalculator«. Diese Engine formt Eingabewerte mit gängigen arithmetischen Ausdrücken um.
Listing 3: Rotation der Erde |
01 #include <Inventor/Qt/SoQt.h>
02 #include <Inventor/Qt/viewers/SoQtExaminerViewer.h>
03 #include <Inventor/SoInput.h>
04 #include <Inventor/nodes/SoSeparator.h>
05 #include <Inventor/nodes/SoSpotLight.h>
06 #include <Inventor/nodes/SoScale.h>
07 #include <Inventor/nodes/SoTexture2.h>
08 #include <Inventor/nodes/SoTranslation.h>
09 #include <Inventor/nodes/SoRotationXYZ.h>
10 #include <Inventor/nodes/SoSphere.h>
11 #include <Inventor/engines/SoTimeCounter.h>
12 #include <Inventor/engines/SoCalculator.h>
13
14 // Code von Listing 1 und 2 hier einfügen
15
16 int main(int argc, char ** argv)
17 {
18 // SoQt initialisieren (erzeugt ein Qt-Fenster)
19 QWidget *fenster = SoQt::init("main");
20
21 // Szenengraph erstellen
22 SoSeparator *wurzel = new SoSeparator;
23 wurzel->ref();
24
25 // Szene ausleuchten: Spotlight erzeugt auch Schatten
26 SoSpotLight *licht = new SoSpotLight;
27 licht->location.setValue(0,0,2);
28 licht->direction.setValue(0,0,-1);
29 licht->cutOffAngle = 1.5;
30 wurzel->addChild(licht);
31
32 // Gruppenknoten für die rotierende Erde
33 SoSeparator *erde = new SoSeparator;
34
35 // Rotationsknoten setzen
36 SoRotationXYZ *erdrotation = new SoRotationXYZ;
37 erdrotation->axis.setValue("Y");
38 erde->addChild(erdrotation);
39
40 // Erde in die Szene einfügen
41 erde->addChild(zeichneErde());
42
43 // Zähler setzen
44 SoTimeCounter *zaehler = new SoTimeCounter;
45 zaehler->max=360;
46 zaehler->step=1;
47 zaehler->frequency=0.03;
48
49 // Werte umrechnen: Grad -> Rad
50 SoCalculator *umrechner = new SoCalculator;
51 umrechner->a.connectFrom(&zaehler->output);
52 umrechner->expression.set1Value(0,"oa=a/(2*M_PI)");
53
54 // Zähler mit Erdrotationsknoten verbinden
55 erdrotation->angle.connectFrom(&umrechner->oa);
56
57 // Gruppenknoten der Erde einfügen
58 wurzel->addChild(erde);
59
60 // Gruppenknoten für das Flugzeug anlegen
61 SoSeparator *flugzeug = new SoSeparator;
62
63 // Flugzeug aus dem Mittelpunkt der Szene schieben
64 SoTranslation *flughoehe = new SoTranslation;
65 flughoehe->translation.setValue(0,0,1.2);
66 flugzeug->addChild(flughoehe);
67
68 // Flugzeug verkleinern und um 90¡ drehen
69 SoScale *skalierung = new SoScale;
70 skalierung->scaleFactor.setValue(0.0025,0.0025,0.0025);
71 flugzeug->addChild(skalierung);
72 SoRotationXYZ *flugrichtung = new SoRotationXYZ;
73 flugrichtung->axis.setValue("Y");
74 flugrichtung->angle = 1.5707963;
75 flugzeug->addChild(flugrichtung);
76
77 // Flugzeuggeometrie aus Datei lesen
78 flugzeug->addChild(ladeGeometrie("boeing767.iv"));
79
80 // Flugzeug in die Szene einfügen
81 wurzel->addChild(flugzeug);
82
83 // Betrachter erzeugen
84 SoQtExaminerViewer *b = new SoQtExaminerViewer(fenster);
85 b->setSceneGraph(wurzel);
86 b->setHeadlight(FALSE);
87 b->show();
88
89 // Fensters anzeigen und auf "Exit" warten
90 SoQt::show(fenster);
91 SoQt::mainLoop();
92
93 // Betrachter und die Referenz zur Szene löschen
94 delete b;
95 wurzel->unref();
96
97 return 0;
98 }
|
Abgehoben
Um das Beispiel zu vervollständigen, enthält die Szene auch noch das Flugzeug aus Abbildung 2. Das Ergebnis zeigt Abbildung 3, den Szenengraphen dazu Abbildung 4. Er enthält keine Beschreibungen von Geometrien oder deren Aussehen. Beides steht entweder in einer externen Datei (über »ladeGeometrie()« gelesen) oder wird in einer Funktion beschrieben (hier »zeichneErde()«). Die verwendeten Knoten sind Gruppen und Transformationen.
Open Inventor bietet viele weitere Möglichkeiten, um mit diesen und anderen Engines Animationen zu programmieren. Die Online-Dokumentation von Coin[2] beschreibt detailliert alle Engines mit ihren Auswirkungen und Möglichkeiten. (fjl)

Abbildung 3: Ein Flugzeug fliegt in 3D über die Erde, die sich dabei unter ihm dreht. Mit Coin ist es leicht, diese Animation zu programmieren.
Infos |
|
[1] Stephan Siemen, “Virtuelle Welt – Interaktive 3D-Welten mit Coin und Qt”: Linux-Magazin 02/03, S. 100 [2] Coin: [http://www.coin3d.org] [3] Open-Source-Variante von Open Inventor: [http://oss.sgi.com/projects/inventor/] [4] TGS: [http://www.tgs.com] [5] Simage-Bibliothek: [ftp://ftp.coin3d.org/pub/coin/src/] [6] 3dc, ein 3D-Konverter: [http://www.on-the-web.ch/3dc/] [7] Zusätzliche Informationen: [http://prswww.essex.ac.uk/stephan/3D/] [8] Dateien zum Artikel: [ftp://ftp.linux-magazin.de/pub/listings/magazin/2003/03/3d/] |
Der Autor |
|
Dr. Stephan Siemen arbeitet als wissenschaftlicher Mitarbeiter an der Essex University (UK). Dort entwickelt er Software zur 3D-Darstellung von Wettersystemen und unterrichtet Studenten in Computergrafik und Programmierung. Auf[7] bietet er weitere Informationen zum Thema. |





