Rundgang inklusive
Wem CGI und HTTP mit Perl nichts Neues mehr bietet, der kann der Skriptsprache nun auch VRML-Code entlocken. Wie man in einer virtuellen Galerie die eigene Bildersammlung ausstellt, zeigt VRGallery.pl .
- Aria Behbehani
- Ramon Wartala
Wer sich schon oft gefragt hat, ob die eigenen Bildersammlungen auf Festplatten oder CD-ROMs ein trostloses Dasein fristen müssen, findet mit VRgallery.pl vielleicht ein Mittel, um deren Attraktivität zu steigern. Wann immer man jemandem seine Schnappschüsse, Power Goos, Webgrafiken oder Renderings zeigen wollte, mußte man bisher einen der zahlreichen auf dem Markt verfügbaren Bildbetrachter benutzen, die eine eher sterile Sicht auf die eigenen Kreationen boten. Natürlich hat nicht jeder Zeit und Muße, sich in ein teures 3D-Programm einzuarbeiten, um Bilder in Szene zu setzen. Dabei soll nun das Perl-Skript VRgallery.pl helfen. Die Idee dazu entstand, als wir zum ersten Mal den CosmoPlayer 2.1 als Netscape Plug-in installierten. Nach einiger Zeit des Suchens von VRML-Objekten im World Wide Web fand sich, wie so oft rein zufällig, das Perl-Paket von Hartmut Palm (Beschreibung in [1, 2]). Es ermöglicht auf sehr einfache Art, eine räumliche Szene in der Beschreibungssprache VRML (Virtual Markup Language; deutsche Referenz in [3] oder englische unter [4]) zu generieren.
Beim Start von VRgallery.pl kann man mit Optionen einige Parameter einstellen. Die beiden wichtigsten sind -m fĂĽr die maximale Anzahl der Bilder, die die Galerie enthalten soll, und -r fĂĽr die Zahl der Bilder pro Galerieraum. Danach durchsucht das Skript das aktuelle oder das per -d angegebene Verzeichnis samt seinen Unterverzeichnissen nach GIF-, JPEG- sowie PNG-Dateien und merkt sich die Fundstellen.
Innerhalb eines vorgefertigten Galerieraums positioniert VRGallery.pl dynamisch die gefundenen Bilder an den Wänden, wobei die Seitenverhältnisse erhalten bleiben. Natürlich passen nur eine begrenzte Anzahl von Bildern in einen Galerieraum. Aus diesem Grund erzeugt das Skript mehrere davon. Sie sind mit Hilfe eines einfachen Menüsystems von jedem Raum zu erreichen. Der aktuelle Raum ist im Menü hervorgehoben, damit der Betrachter immer weiß, wo er sich aufhält. Für jeden Raum erzeugt das Skript eine eigene VRML-Datei (Endung .wrl).
Dadurch muß der VRML-Viewer nur einige Objekte laden und erlaubt so ein flüssigeres Manövrieren. Gerade bei großen Bilddateien wirkt sich das positiv aus. Besuchen kann man die gesamte Bildersammlung mit einem gewöhnlichen VRML 2.0-Viewer (siehe VRML-Ansichten); Perl und VRgallery sind also nur einmal zum Erzeugen notwendig. Wer schneller auf die Bilder zugreifen möchte, kann dies über vordefinierte Kamerapositionen (Viewpoints) tun. Wählt der Anwender eine davon (der CosmoPlayer realisiert dafür eine eigene Viewpoint-Liste), "fährt" die Benutzeransicht auf diesen Punkt und man erhält eine andere Sicht auf die Szene.
Per Hyperlink zum Original
Jedes Bild ist zusätzlich über einen Hyperlink mit der Originaldatei verknüpft. Ein einfacher Klick darauf erzeugt ein neues Browser-Fenster, das es in der ursprünglichen Größe zeigt. Außerdem steht dem Benutzer eine Rundgangfunktion zur Verfügung, die ihn per Klick auf Tour durch einen Galerieraum führt.
Listing VRgallery.pl
Alle Listings per FTP (VRgallery.pl ohne Zeilennummern und vrgallery.wrl)
1 #!/usr/bin/perl
2 # VRgallery - ein einfacher Ansatz fĂĽr eigene Bildergalerien mit Perl und VRML
3 # (c) 1998 Ramon Wartala (wartala@stud.fh-heilbronn.de).
4 # Galerie-Konstruktion und -Design (c) 1998 Aria Behbehani (ariab@tin.it).
5 # Perl VRML Paket (c) Hartmut Palm
6 # Image::Size Paket (c) Randy Ray
7 #
8 # letzte Änderung 11.06.1998, 02:05 Uhr
9
10 use strict;
11 require 5.000;
12 use Getopt::Std; # Paket zur Verarbeitung von Kommandozeilen-Schaltern
13 use File::Find; # Paket zur Verarbeitung von Datei-Bäumen
14 use Image::Size; # Paket zur Ermittlung von Bildgrößen
15 use VRML; # Paket zur Erzeugung von VRML-Welten
16
17 use vars qw(
18 $opt_m $opt_d $opt_g $opt_v $opt_r $dev $ino $mode $nlink $uid
19 $gid $rdev $size $atime $mtime $ctime $blksize $blocks
20 );
21
22 my $VERSION="0.9.5"; # VRgallery Versionsnummer
23 my $startdir = ""; # Startverzeichnis
24 my $maximg = 0; # Maximale Anzahl Bilder
25 my $ipr = 9; # Anzahl Bilder pro Raum
26 my $counter = 0; # Bilder-Index
27 my $verbose = 0; # Mit Ausgabe (1) oder ohne (0)
28 my $usegzip = 0; # GZIP-Schalter
29 my @images = (); # Feld mit allen Bilderpfaden
30
31 # Durchsuche ab Startverzeichnis alle Verzeichnisse und sichere die Bilderpfade
32 sub search_dir {
33 if ($counter == $maximg) {
34 $File::Find::prune = 0;
35 } else {
36 if ($File::Find::name =~ '.jpg|.JPG|.jpeg|.JPEG|.gif|.GIF|.png|.PNG') {
37 push @images, $File::Find::name;
38 $counter++;
39 }
40 }
41 }
42
43 # Kommandozeilen-Optionen ermitteln
44 sub parse_args {
45 if($#ARGV == -1) {
46 print "Aufruf: VRgallery\n";
47 print "\t-m <nummer> maximale Anzahl verwendeter Bilder\n";
48 print "\t-r <nummer> Anzahl Bilder pro Raum (<=9)\n";
49 print "\t-d Startverzeichnis\n";
50 print "\t-g Soll GZIP zum Packen der Szenen benutzt werden\n";
51 print "\t-v verbose\n\n";
52 print "Beispiel: VRgallery -d c:\\myimages -m 9 -r 9 -g -v\n";
53 exit(-1);
54 } else {
55 getopts('m:r:vgd:');
56
57 if($opt_v) { $verbose = 1 }
58
59 if($opt_m) {
60 $maximg = $opt_m;
61 print "Suche nach maximal $maximg Bildern","\n" if $verbose;
62 }
63
64 if ($opt_d) {
65 $startdir = $opt_d;
66 } else { $startdir = "." }
67
68 print "Durchsuche $startdir","\n" if $verbose;;
69 finddepth(\&search_dir,$startdir);
70 #find(\&search_dir,$startdir);
71
72 if($opt_g) { $usegzip = 1 }
73
74 if($opt_r) {
75 if($opt_r <= $ipr) {
76 $ipr = $opt_r
77 }
78
79 if($ipr > 1) {
80 print "$ipr Bilder pro Raum","\n" if $verbose;
81 } else {
82 print "nur ein Bild pro Raum","\n" if $verbose;
83 }
84 }
85 }
86 }
87
88 # Positioniert Kameras auf die entsprechenden Bilder
89 sub pos_camera {
90 my ($gallery, $order, $image) = @_;
91 my $picname = "";
92
93 my %campos = (
94 frontfront => ["0.0 0.0 2.2","0 1 0 -3.142"],
95 frontright => ["1.0 0.0 4.0","0 -1 0 -1.8"],
96 frontleft => ["-1.0 0.0 4.0","0 1 0 -1.8"],
97 rightfront => ["-2.2 0.0 0.0","0 -1 0 -1.571"],
98 rightright => ["-5.0 0.0 2.0","0 -1 0 -0.2"],
99 rightleft => ["-5.0 0.0 -2.0","0 1 0 -3.142"],
100 leftfront => ["2.2 0.0 0.0","0 1 0 -1.571"],
101 leftleft => ["5.0 0.0 2.0","0 -1 0 -0.2"],
102 leftright => ["5.0 0.0 -2.0","0 1 0 -3.142"],
103 );
104
105 $gallery->insert("Viewpoint {")
106 ->insert(" description \"Picture: $image\"")
107 ->insert(" position $campos{$order}[0]")
108 ->insert(" orientation $campos{$order}[1]")
109 ->insert(" fieldOfView 0.7")
110 ->insert("}");
111 }
112
113 sub pos_image {
114 my ($gallery, $order, $image) = @_;
115 my $width = 0;
116 my $height = 0;
117 my @q_scale =("1","3","3");
118 my @l_scale =("1","3","3.5");
119 my @p_scale =("1","4.5","3");
120 my $scale = "";
121
122 # Bilderrahmen-Position als Hash: trans[0-2], rot[3-6], rot_tex[7]
123 my %picframe =(
124 rightfront => ["-5.8","-0.3712","-0.02398","0","0","0","-1.571","-3.15"],
125 leftfront => ["5.99","-0.3712","-0.02398","0","0","0","-1.571","0"],
126 frontfront => ["0","-0.3712","5.99","0","1","0","-4.713",-3.15],
127 frontright => ["-2.65","-0.3937","4.8","0","1","0","0.25","-3.15"],
128 frontleft => ["2.625","-0.3937","4.8","0","1","0","-3.37","-3.15"],
129 rightright => ["-4.761","-0.3937","-2.6","0","1","0","-1.326","-3.15"],
130 rightleft => ["-4.761","-0.3937","2.7","0","1","0","1.326","-3.15"],
131 leftright => ["4.745","-0.3937","2.65","0","1","0","-1.326","0"],
132 leftleft => ["4.745","-0.3937","-2.65","0","1","0","-1.812","-3.15"],
133 );
134
135 # Bildhöhe und Bildbreite zur Bestimmung der Rahmenproportionen ermitteln
136 ($width,$height) = imgsize($image);
137
138 if ($width > $height) {
139 $scale = "scal $l_scale[0] $l_scale[1] $l_scale[2]";
140 } elsif ($width == $height) {
141 $scale = "scal $q_scale[0] $q_scale[1] $q_scale[2]";
142 } else {
143 $scale = "scal $p_scale[0] $p_scale[1] $p_scale[2]";
144 }
145
146 # Das Bild und der Link auf die Originaldatei
147 $gallery->comment("Bilderrahmen mit Bild $image");
148 $gallery->insert("Anchor {url \"$image\" parameter \"target=_blank\" children [")
149 ->insert("PictureFrame {
150 trans $picframe{$order}[0] $picframe{$order}[1] $picframe{$order}[2]
151 rot $picframe{$order}[3] $picframe{$order}[4] $picframe{$order}[5] $picframe{$order}[6]
152 $scale
153 picture_tex \"$image\"
154 rot_tex $picframe{$order}[7]
155 }")
156 ->insert("]}");
157
158 # Kameras mit direkter Sicht auf die Bilder positionieren
159 &pos_camera($gallery,$order,$image);
160 }
161
162 # Erzeugt MenĂĽ- und den Tour-Button
163 sub create_menu {
164 my($gallery,$no_of_rooms,$actual_room) = @_;
165 my $i = 0;
166 my $ypos = -2;
167
168 $gallery->comment("Rundgang TouchSensor");
169
170 $gallery->transform_begin("t=0.0 -2.5 -10.0")
171 ->billboard_begin("0 1 0",undef,"0 0 0")
172 ->touchsensor("TourButton")
173 ->transform_begin("t=0.0 0.0 -0.1")
174 ->box("1.5 0.5 0.1","d=0.6314 0.6314 0.6314;sh=0.92;tr=0.5")
175 ->transform_end
176 ->transform_begin("t=-0.6 -0.1 0.0")
177 ->text("tour","white","0.5 SERIF BOLD","BEGIN")
178 ->transform_end
179 ->billboard_end
180 ->transform_end;
181
182 $gallery->route("TourButton.touchTime","TourClock.startTime");
183 $gallery->route("TourButton.touchTime","TurnClock.startTime");
184 $gallery->route("TourClock.fraction_changed","TourPos.set_fraction");
185 $gallery->route("TurnClock.fraction_changed","TourOri.set_fraction");
186 $gallery->route("TourOri.value_changed","TourEye.set_orientation");
187 $gallery->route("TourPos.value_changed","TourCam.set_translation");
188
189 $gallery->comment("Menue mit Links zu den einzelnen Raeumen");
190
191 for($i=1;$i <= $no_of_rooms;$i++) {
192 $gallery->transform_begin("t=0.0 $ypos -10.0")
193 ->billboard_begin("0 1 0",undef,"0 0 0")
194 ->anchor_begin("room$i.html","room$i.wrl",undef,undef,"0 0 0")
195 ->transform_begin("t=0.0 0.0 -0.1")
196 ->box("1.5 0.5 0.1","d=0.6314 0.6314 0.6314;sh=0.92;tr=0.5")
197 ->transform_end
198 ->transform_begin("t=-0.6 -0.1 0.0");
199 if($i == $actual_room) {
200 $gallery->interpolator("BlinkInterpol","Color",[0,1],["0.6 0.6 0.6","0.9 0.7 0.02"]);
201 $gallery->timesensor("BlinkTime","3","TRUE");
202
203 $gallery->insert("Shape { appearance Appearance { material DEF TextMaterial Material {
204 diffuseColor 1 1 1 }}geometry Text { string \"room $i\" fontStyle FontStyle { size 0.5
205 family \"SERIF\" style \"BOLD\" justify \"BEGIN\" } } } # Shape");
206 } else {
207 $gallery->text("room $i","white","0.5 SERIF BOLD","BEGIN");
208 }
209
210 $gallery->transform_end
211 ->anchor_end
212 ->billboard_end
213 ->transform_end;
214
215 if($i == $actual_room) {
216 $gallery->route("BlinkTime.fraction_changed","BlinkInterpol.set_fraction");
217 $gallery->route("BlinkInterpol.value_changed","TextMaterial.set_diffuseColor");
218 }
219
220 $ypos=$ypos+0.5
221 }
222 }
223
224 # Erzeugt die eigentliche Galerie
225 sub create_gallery {
226 my $no_of_pics = 0; # Anzahl Bilder
227 my $gallery = undef; # VRML Objekt
228 my $no_of_rooms = 0; # Anzahl der Räume
229 my $nor = 0.0;
230 my $nor2 = 0;
231 my $image = ""; # Aktuelles Bild
232 my $ic = 0; # Bilder-Index
233 my $gc = 0; # Galerie-Index
234 my $rest = 0;
235 my $order_idx = 0; # Reihenfolge Index
236 my $i = 0;
237 my $j = 0;
238
239 # Reihenfolge des Aufhängens der Bilderrahmen
240 my @picord =("frontfront","rightfront","leftfront",
241 "frontleft","frontright","leftleft","leftright",
242 "rightleft","rightright","exitleft","exitright"
243 );
244
245 $no_of_pics = scalar @images;
246 print "$no_of_pics Bilder gefunden\n" if $verbose;
247
248 # Wieviel Räume soll die Galerie haben?
249 $nor= $no_of_pics / $ipr;
250 $nor2 = int($nor);
251 if(abs($nor) > $nor2) {$nor2++}
252 $no_of_rooms = $nor2;
253
254 if ($no_of_rooms > 1) {
255 print "Erzeuge $no_of_rooms Räume für die Galerie...","\n" if $verbose;
256 } else {
257 print "Erzeuge nur einen Raum fĂĽr die Galerie...","\n" if $verbose;
258 }
259
260 $rest = $no_of_pics;
261
262 for($j=1;$j <= $no_of_rooms;$j++) {
263 $gc++;
264 $order_idx = 0;
265 print "Erzeuge Raum Nr. $gc\n" if $verbose;
266
267 $gallery = new VRML(2);
268 $gallery->comment("Diese VRML 2.0 Datei wurde mit VRgallery $VERSION erzeugt, (c) 1998 by R. Wartala, A. Behbehani");
269 $gallery->comment("");
270 $gallery->browser("CosmoPlayer","Microsoft Internet Explorer");
271 $gallery->title("Raum $gc, erzeugt mit VRgallery $VERSION - (c) 1998 by R. Wartala, A. Behbehani");
272 $gallery->navigationinfo("ANY",1.0,0,0.0,"0.25 1.8 0.75");
273
274 # Mr. Behbehani's Galerie einbinden
275 $gallery->include("vrgallery.wrl");
276
277 if($rest > $ipr) {
278 for($i=1;$i <= $ipr;$i++) {
279 print "$ic. Bild $images[$ic] fĂĽr Galerieraum $gc\n" if $verbose;
280 &pos_image($gallery,$picord[$order_idx],$images[$ic]);
281 $ic++;
282 $order_idx++;
283 if ($order_idx == 9) {
284 $order_idx = 0;
285 }
286 $rest--;
287 }
288 } else {
289 for($i=1;$i <= $rest;$i++) {
290 print "$ic. Bild $images[$ic] fuer Galerieraum $gc\n" if $verbose;
291 &pos_image($gallery,$picord[$order_idx],$images[$ic]);
292 $ic++;
293 $order_idx++;
294 if ($order_idx == 9) {
295 $order_idx = 0;
296 }
297 }
298 }
299
300 # Raum-Auswahlliste erzeugen
301 &create_menu($gallery,$no_of_rooms,$gc);
302
303 # VRML-Quellcode speichern
304 if($usegzip == 1) {
305 $gallery->save("room$gc.wrl","gzip");
306 } else {
307 $gallery->save("room$gc.wrl");
308 }
309
310 # HTML-Seite mit eingebetteter VRgallery erzeugen
311 open(HTML,">room$gc.html") or die "can't create html dummy!\n";
312 print HTML "<HTML><HEAD><TITLE>VRgallery: room$gc</TITLE></HEAD>\n";
313 print HTML "<BODY LINK=\"#f9bc09\" TEXT=\"#aaaaaa\" BGCOLOR=\"#000000\"><CENTER><TABLE BORDER=\"0\"><TR><TD>\n";
314 print HTML "<EMBED SRC=\"room$gc.wrl\" HEIGHT=\"480\" WIDTH=\"640\">\n";
315 print HTML "</TD></TR>\n";
316 print HTML "<TR ALIGN=\"center\" VALIGN=\"middle\"><TD><FONT SIZE=\"2\"><I>Created with VRgallery $VERSION";
317 print HTML ", © 1998 by <A HREF=\"mailto:wartala\@stud.fh-heilbronn.de\">R. Wartala</A>,";
318 print HTML " <A HREF=\"mailto:ariab\@tin.it\">A. Behbehani.</A></FONT>\n";
319 print HTML "<FONT SIZE=\"2\"> Best view with <A HREF=\"http://www.cosmosoftware.com\">";
320 print HTML "<IMG SRC=\"cplayer21.gif\" BORDER=\"0\" ALIGN=\"middle\"></A></FONT></I></TD></TR>\n";
321 print HTML "</TABLE></CENTER></BODY></HTML>\n";
322 close HTML;
323 }
324 }
325
326 # Hier geht's los...
327 print "VRgallery ",$VERSION," - (c) 1998 by R. Wartala, A. Behbehani","\n";
328 &parse_args();
329 &create_gallery();
330
Im Listing sind die entscheidenden Teile von VRgallery.pl zu sehen. Nachdem die Routine parse_args ab Zeile 44 die Kommandozeilenoptionen eingelesen und analysiert hat, sucht das Skript in Zeile 69 nach Bilddateien. Sein Dreh- und Angelpunkt ist die Routine create_gallery in Zeile 225. Sie leistet innerhalb von zwei Schleifen die Hauptarbeit. Nach dem Anlegen eines neuen VRML-2.0-Objekts mit
$gallery = new VRML(2);
in Zeile 267 kommt acht Zeilen später der Hauptteil der Galerie aus der externen Datei vrgallery.wrl mit
$gallery->include("vrgallery.wrl");
hinzu. Zuvor muß die Routine noch berechnen, wie viele Galerieräume sie erzeugen soll. Dies ergibt sich aus der Zahl gefundener Bilder und der festgelegten Anzahl Bilder pro Raum. Aus bautechnischen Gründen können in jedem Raum nur maximal neun Bilder hängen. Deren Verteilung erfolgt nach einem festen Schema, das im Feld picord gespeichert ist. Hier sind leicht Anpassungen an die eigenen Vorlieben möglich. Die eigentliche Positionierung übernimmt pos_image(). Im Hash %picframe sind alle Koordinatentransformationen, Rotationen und Texturtransformationen für diesen Zweck gespeichert. In Zeile 136 wird mit
($width,$height) = imgsize($image);
die Größe des Bildes in Pixeln festgestellt. Diesen Befehl stellt das Image::Size-Paket von Randy Ray bereit. Mit ihm läßt sich die Größe von JPEG-, GIF-, PNG- und TIFF-Bildern herausfinden, die zur Skalierung nötig ist.
MenĂĽ per Billboard-Knoten
Nun kann man Bilderrahmen samt Bilddateitextur in die VRML-Szene einsetzen. Zuvor erstellt das Skript noch eine Verknüpfung auf die Bilddatei mit Hilfe eines Anchor-Knotens. Als Target dient _blank. Ein Klick auf die Bilder erzeugt so ein neues Browser-Fenster, das das Original zeigt. Schließlich sind die einzelnen Räume, wenn es mehrere geben sollte, noch mit einem entsprechenden Menü zu verknüpfen. Diese Aufgabe übernimmt create_menu() in Zeile 163. Das Menü besteht aus sogenannten Billboard-Knoten. Dieser VRML-Objekttyp versucht immer, sein lokales Koordinatensystem auf die aktuelle Position des Betrachters auszurichten, damit seine Grafik für diesen stets sichtbar bleibt. Jedes Billboard enthält einen Text mit der Nummer eines Raums (zum Beispiel "room 1"). Den Raum, in dem sich der Betrachter momentan aufhält, zeigt dieses Menü ebenfalls an. Mit Hilfe eines ColorInterpolators und eines TimeSensors erscheint dieser Text blinkend, so daß er sich von den anderen abhebt.
Jetzt kann die VRML-Datei eines Galerieraums endlich erzeugt werden. Wer gzip installiert hat, kann die so erstellten Räume mit dem Kommandozeilen-Flag -g komprimieren lassen. Doch Vorsicht: Nicht jeder VRML-Browser unterstützt dies. Zum Schluß erstellt das Skript noch eine HTML-Seite, die den VRML-Raum einbettet. Diese können Anwender später mit einem geeigneten WWW-Browser aufrufen.
Eine separate Datei enthält die Vorlage des Galerieraums. Die Bilderrahmen sind mit Hilfe von Prototypen aus VRML 2.0 realisiert (siehe Prototypen und Animationen). Auf Grund der zusätzlich zu den Bilderdaten entstehenden Datenmenge war die Galerie aus möglichst wenigen, einfachen und kompakten Elementen zu generieren. Der Raum selbst basiert deshalb auf einem achteckigen Grundriß (siehe Abbildung). Die für ein Bild zur Verfügung stehende Fläche ist durch die Lichtschlitze in den Wänden begrenzt und bietet dem Betrachter eine definierte Struktur, damit eine Orientierungshilfe. Das Zusammenspiel der einzelnen Konstruktionselemente (Raum, Innenwände, Tür und Lichtschlitze) spielt eine wichtige Rolle für die Wahrnehmung der Raumverhältnisse. Erst durch sie bekommt der Betrachter einen realistischen Maßstab, da er sie in einen Zusammenhang mit Objekten aus der wirklichen Welt sieht.
VRgallery.pl soll nur demonstrieren, wie einfach und schnell man dreidimensionale Welten mit Hilfe von Perl und VRML erzeugen kann. Dabei sind der Phantasie kaum Grenzen gesetzt. Denkbar wären etwa einige wenige Modifikationen, um es als Database Publishing System für ein ECommerce Systeme einzusetzen. Begrenzender Faktor dabei ist höchstens die Rechenleistung des eingesetzten Prozessors und der Bildschirmspeicher sowie die Triangulierungsleistung der verwendeten Grafikkarte. Da beides jedoch ständig schneller und billiger wird, ist die VRgallery schon auf kleineren Pentium Systemen einsetzbar.
Ramon Wartala
studiert Medizinische Informatik an der Universität Heidelberg/FH-Heilbronn.
Aria Behbehani
studiert Architektur am Polytechnikum in Mailand.
Literatur
[1] Rolf Dräßler, Hartmut Palm; Virtuelle Informationsräume mit VRML; 1. Auflage 1998; dpunkt-Verlag Heidelberg; ISBN 3-920993-78-0
[2] http://www.gfz-potsdam.de/~palm
[3] Hans-Lothar Hase; Dynamische virtuelle Welten mit VRML 2.0; 1. Auflage 1997; dpunkt-Verlag Heidelberg; ISBN 3-920993-63-2
[4] http://vag.vrml.org
iX-TRACT
- VRgallery.pl erzeugt aus GIF-, PNG- und JPEG-Bildern eine virtuelle Gallerie im VRML-Format.
- Um die Datenmenge zu begrenzen, benutzt das Skript VRMLs PROTO-Typen, die wie Subroutinen benutzbar sind.
- Zur Navigation in den Räumen dient ein Menü aus Billboard-Knoten. Ihre Grafik richtet sich stets auf den Betrachter aus.