#! /usr/bin/perl # # Integrate Geomview with 3D data on the Web. # Filter for VRML, WebOOGL, or OOGL 3D data files. # # To use, put the following lines in your .mailcap file: # object/x-oogl; to-weboogl.perl %s # world/x-oogl; to-weboogl.perl %s # x-world/x-vrml; to-weboogl.perl %s # # This script depends on the following other files: # to-weboogl.perl # vrml2oogl # woogl2oogl # gvgeturl.perl # and the libwww-perl installation. # # This script runs as a Geomview external module, # converting VRML or WebOOGL into the true OOGL # understood by Geomview. # # It is invoked by the to-weboogl.perl wrapper script, which keeps # track of communication between Geomview and the 2D Web browser. # # In detail, it can either be started with a "0" and # an optional URL: # # "geomview -run weboogl.perl 0 http://www.server/foo.oogl" # # or with the process id of a running Mosaic or Netscape process # and an optional local WebOOGL file: # # "geomview -run weboogl.perl 12345 /tmp/foo.weboogl" # # # by Tamara Munzner, Paul Burchard, Stuart Levy # {munzner|burchard|slevy}@geom.umn.edu # Copyright 1995 The Geometry Center # Publically distributable according to the terms of # the GNU General Public License. # # Drastic rewrite March 1995 Tamara Munzner # Original version June 1994 # The old version used inlined perl code to get URLs, this version # uses libwww-perl. Thus firewalls/proxies are handled. # And relative URLs if we're lucky enough to get a base URL from # the 2D Web browser. # In many places, variable names and comments still say "Mosaic" when # they should just say "2D Web browser". # ######################################################################## ######################################################################## ## Main program. ## ## After setup, listen for user picks from Geomview, and possibly for ## new OOGL data files from Mosaic. ######################################################################## ######################################################################## package main; # Deal with signals... # # Clean up on exit. $SIG{'HUP'} = 'exit_cleanup'; # # Respond to requests from Mosaic. $SIG{'INT'} = 'mosaic_request'; # Deal with I/O... # # Flush all buffers promptly. select(STDERR); $| = 1; select (STDOUT); $| = 1; # # Use TOGV instead of just plain STDOUT for later flexibility. open(TOGV,">-"); select(TOGV); $| = 1; # Prepare Geomview... # # Collect some common operations for future reference. $world_geom = 'www'; $new_world_begin = "(delete world)\n"; $recenter="\n(progn (new-center World)(new-center c0)(echo \"\n\"))\n"; $pick_back = "(backcolor allcams 0.333 .5 0.333)\n"; # flash green $search_back = "(backcolor allcams .5 0.333 0.333)\n"; # flash red $convert_back = "(backcolor allcams 0.333 0.333 .5)\n"; # flash blue $idle_back = "(backcolor allcams 0.333 0.333 0.333)\n"; # unflash grey # # Tell Geomview we want to hear about pick events. print TOGV "(interest (pick primitive * * nil nil nil * nil nil nil))\n"; #print TOGV "(interest (pick universe * * nil nil nil * nil nil nil))\n"; # # Tell Geomview we want to hear if the user changes the background color. print TOGV "(interest (backcolor focus))\n"; # # Tell Geomview to tell us when changing from Euclidean to hyperbolic space. # $space = "euclidean"; print TOGV "(space $space)\n"; print TOGV "(interest (space))\n"; # # We care when the windowsize changes # print TOGV "(interest (camera-reset))\n"; # # Define alphabet handles in Geomview (for labels). &loadfont(); # # Turn off bounding boxes, normalization, and pick feedback. print TOGV "(bbox-draw allgeoms no)\n"; print TOGV "(normalization allgeoms none)\n"; print TOGV "(look-encompass-size 1.0 100 0.1 4.0)\n"; print TOGV "$idle_back"; # Process command args... # # First argument is either zero, or pid of associated HTML browser. # The optional second arg is either an initial URL, if $ARGV[0] is zero, # or a local WebOOGL file to load, if $ARGV[0] is non-zero. $original_mosaic_pid = $mosaic_pid = 0; $newworldurl = "NULL"; print TOGV "$new_world_begin"; if($#ARGV >= 0) { # some browsers, like netscape1.1S, will obligingly give us a # base URL so that we can deal properly with relative URLs. $contype = $ARGV[2] if ($#ARGV >= 2); $baseurl = $ARGV[3] if ($#ARGV >= 3); $original_mosaic_pid = $mosaic_pid = $ARGV[0]; if($mosaic_pid != 0) { &mosaic_open_comm(); # Make local file be the current "world". if($#ARGV >= 1) { &localfile_to_oogl_cached($ARGV[1]); } } elsif($#ARGV >= 1) { # Make URL be the current "world". &url_to_oogl_cached($ARGV[1], $world_geom); } } print TOGV " $recenter "; # Put up control panel $actionmode = "about"; &setup_controls; # Main loop: interact with Geomview and 2D browser # # When Geomview sends back a pick event, get the associated COMMENT # objects which have embedded HREFs. No more parsing the # geometry in perl, it's done properly inside of Geomview. # # When Mosaic (via a signal from an external viewer) sends us # an unsolicited name of a new WebOOGL file to load, use it to # replace the current world geom. [This is done in a signal # handler below, not in the main loop. However, when the signal # interrupts , the read operation apparently returns false, # so we have keep the main loop going for a while. # However, in case our parent geomview just died, don't loop forever; # give up after we've encountered EOF or other read failures many # times in a row without response from Geomview. Note that we # always make Geomview echo something when sending it new geometry, # so there is no problem if the user only ever sends data in the # Mosaic->Geomview direction.] # # $gvfailcount = 20; $leftovers = ""; while(($rest = ) || --$gvfailcount >= 0) { $gvfailcount = 20 if defined($rest); while ( $rest ) { # keep parsing until we've used up the line $line = $leftovers . $rest; if ($line =~ /\(\s*camera-reset([^\)]*)\)(.*)/) { print TOGV "(merge camera controls {fov $fov})\n"; $rest = $2; $leftovers = ""; redo; } if ($line =~ /\(\s*backcolor\s+focus\s+([\d\.\s]*)\s*\)(.*)/) { # unflash background to the color the user set $idle_back = "(backcolor allcams $1)\n"; $rest = $2; $leftovers = ""; redo; } #end of backcolor processing if ($line =~ /\(\s*space\s+(\S*)\s*\)(.*)/) { # adjust the control panel to look right in either space $space = $1; if ($space eq "hyperbolic") { $fov = $hypfov; $recenter = "(echo \"\n\")"; } elsif ($space eq "euclidean") { $fov = $eucfov; $recenter="\n(progn (new-center World)(new-center c0)(echo \"\n\"))\n"; } print TOGV "(merge camera controls {fov $fov})\n"; $rest = $2; $leftovers = ""; redo; } #end of space processing if ($line =~ /\(\s*NeedURL\s+(\S*)\s*\)(.*)/) { # emodule transmission from woogl2oogl or vrml2oogl, # we ran across an inlined URL. # $url does double duty. It's both a reference to # an actual URL, and the name of the OOGL handle # that needs to be defined with the eponymous content. $url = $1; &url_to_oogl_cached($url, $url); $rest = $2; $leftovers = ""; redo; } #end of NeedURL processing if ( $line =~ /\(\s*RegisterURL\s+(\S*)\s*\)(.*)/ ) { # emodule transmisssion from gvgeturl.perl: # cache this oogl/weboogl/vrml url $url = $1; @urls[$urlcount++] = $url; if ($url eq $newworldurl) { $newworldurl = "NULL"; print TOGV " (geometry $world_geom {: \"$url\"})"; } print TOGV "$recenter"; $rest = $2; $leftovers = ""; redo; } #end of RegisterURL processing if ( $line =~ /\(pick\s+(\S*)\s+(\S*)\s+\(([^\)]*)\)\s+[^\(]*\(([^\)]*)(.*)/ ) { $coordsys = $1; $id = $2; $vert = $3; $primarray = $4; if ($id eq "controls") { local($x,$y,$z,$w) = split(' ',$vert); if ($x < 0) { $actionmode = "follow"; print TOGV "$aboutoff $followon\n"; } elsif ($x > 0) { $actionmode = "about"; print TOGV "$abouton $followoff\n"; } } else { # $primarray is the "pick path", a list of integers # which specify which subpart of the hierarchical OOGL # object was picked. Descent into a complex object # (LIST or INST) adds a new integer to the path. Traversal of # simple objects increments the counter at the current # level. # In the older version of weboogl we had to ask Geomview # to write out the entire geometry, slowly parse it # ourselves in perl, and understand the vagaries of OOGL # structures enough to figure out which COMMENT to use. # Armed the new write-comments command, we will make # Geomview do the work faster, cleaner, and better. print TOGV "(write-comments - $id ($primarray))\n"; } $rest = $5; $leftovers = "";redo; } # end of pick processing if ( $line =~ /\(\s*(({\s*COMMENT.*\s+(HREF\s*{\s*[^{}]*}|HTITLE\s*{\s*\"[^\"]*\"\s*})\s*})*\s*\))(.*)/ ) { # get the *entire* string, delimited by parens # note that there may be parens embedded in quoted strings # in HTITLE info, don't be fooled by these... # Note that the pick might match more than one comment. # The right choice depends on the structure of the # WebOOGL object and the intent of a particular # application. Given the structure of the demo objects, # the "leaf" comment comes last in the string. $urllevel = 0; $titlelevel = 0; @urlcomments = ""; @titlecomments = ""; $nextcomment = $1; $rest = $2; $leftovers = ""; while ( $nextcomment =~ s/(\s*{\s*COMMENT.*\s+(HREF\s*{\s*[^{}]*}|HTITLE\s*{\s*\"[^\"]*\"\s*})\s*})// ) { $firstcomment = $2; $firstcomment =~ /(\S*)\s+{\s*\"?(.*)\"?}$/; $commenttype = $1; $commentinfo = $2; if ($commenttype =~ /[Hh][Rr][Ee][Ff]/) { @urlcomments[$urllevel++] = $commentinfo; } elsif ($commenttype =~ /[Hh][Tt][Ii][Tt][Ll][Ee]/) { @titlecomments[$titlelevel++] = $commentinfo; } } $commentstring = @urlcomments[$#urlcomments]; $commentstring =~ s/\s+/\ /g; $commentstring =~ s/\s*$//; ($url,$modifiers) = ($commentstring =~ /\s*[\"]?([^\s\"]*)[\"]?(.*)/); redo if ($commentstring eq ""); if ($actionmode eq "follow") { print TOGV "$search_back"; # deal with the URL &url_to_oogl_cached($commentstring, $world_geom); # while we're at it... &genlabels(@titlecomments[$#titlecomments],"title"); &genlabels($url, "url"); print TOGV "$idle_back"; } elsif ($actionmode eq "about") { print TOGV "$flash\n"; &genlabels($url,"url"); &genlabels(@titlecomments[$#titlecomments],"title"); print TOGV "$abouton\n"; } redo; } #end of comment processing if ($line =~ /\( \)(.*)/) { #picked geom had no embedded comment $rest = $1; redo; } $leftovers = $line; last; } # end of line parsing } # end of infinite read-respond loop # end of main program &exit_cleanup(); # Clean up when killed or when dying on error. sub cleanup { # Shut down communications with Mosaic, cleaning up comm files. &mosaic_close_comm(); # wash behind ears: remove all temporary files # delete /tmp/*weboogl*. opendir(D, "/tmp"); @allfiles = grep(/weboogl/, readdir(D)); foreach $f (@allfiles) { unlink("/tmp/$f") if (-w "/tmp/$f"); } closedir(D); 1; } sub exit_cleanup { &cleanup(); exit(0); } sub die { local($string) = @_; &cleanup(); die $string; } ######################################################################## # Caching and retrieval of OOGL files. ######################################################################## # Process a URL, converting to OOGL if possible. sub url_to_oogl_cached { local($url, $newworld) = @_; local($realurl, $modifiers); # Extract the URL and follow the link. Make sure to # strip off quotes so URLs with special chars can be made. ($realurl,$modifiers) = ($url =~ /\s*[\"]?([^\s\"]*)[\"]?(.*)/); if($modifiers =~ /Embed/io) { # If the WebOOGL comment contains the "rel WorldEmbed" modifier, # add the geom to the world, instead of replacing the world. # (For now we just grep for "Embed"...) printf TOGV "(geometry %s%d { : \"$realurl\" })", $world_geom, $embed++; # print STDERR "EMBEDDING\n"; $newworld = ""; } if (&checkcache($url)) { # Already downloaded and instantiated OOGL handle of this URL. if ($newworld eq $world_geom) { print TOGV " (geometry $world_geom {: \"$realurl\"}) $recenter"; } } else { # The URL is new. Tell Geomview to spawn a process to deal with it. # Deal with means download (with libwww-perl) and then # convert if oogl/weboogl/vrml, # pass off to 2D Web browser if something else. if ($newworld eq $world_geom) { $newworldurl = $realurl; } print TOGV "(emodule-run \'gvgeturl.perl $realurl $mosaic_pid \')\n"; } 1; } # Convert local WebOOGL file to OOGL # Slight misnomer: currently local files aren't effectively cached, # since no URL info available. sub localfile_to_oogl_cached { local($rawfile) = @_; chop $rawfile if ($rawfile =~ /\n$/); local($base) = " -base $baseurl " if $baseurl; $newworldurl = "file://localhost$rawfile"; print TOGV "(emodule-run \'gvgeturl.perl file://localhost$rawfile $mosaic_pid -contype $contype $base > $rawfile.tmp; echo \"(load $rawfile.tmp command)\"\')\n"; 1; } #Check the cache to see if we've already downloaded this URL sub checkcache { local ($url) = @_; local ($downloaded) = 0; foreach $key (@urls) { # Note that $urls{$key} may contain modifiers in addition to the URL. # However this doesn't disturb our search since URLs are space-free. # Also, the URL may be enclosed in quotes for protection. if ($key =~ /\s*[\"]?$url[\"]?\s*$/) { $downloaded = 1; # print STDERR "$url already cached\n"; #!!!# last; } } # don't add to the URL array here, since it might be a non-oogl URL. # wait until we get a RegisterURL message from gvgeturl.perl. #return success if cache hit $downloaded ? 1 : 0; } ######################################################################## # Communications with Mosaic. ######################################################################## # Handle request from Mosaic. # This routine is invoked in response to a signal from Mosaic. sub mosaic_request { local($rawfile,$tmp,$comm_file); # The communications file "/tmp/WebOOGL.$mosaic_pid" contains # the name of an unsolicited 3D file to open. We delete # the communications file immediately as an acknowledgement. # # We then process the 3D file into real OOGL, load the # data into Geomview and delete the original WebOOGL file. # Make sure we get the next request too. $SIG{'INT'} = 'mosaic_request'; $comm_file = "/tmp/WebOOGL.$mosaic_pid"; if(open(REQUEST, "<$comm_file")) { if($rawfile = ) { #snag baseurl if there $baseurl = $tmp if ($tmp = ); # zap comm file close(REQUEST); unlink($comm_file); # process 3D file and load into Geomview &localfile_to_oogl_cached($rawfile); unlink($rawfile); } else { # zap comm file in any case close(REQUEST); unlink($comm_file); } } 1; } sub mosaic_open_comm { # Create control file with our pid, where Mosaic can find it. open(CONTROL, ">/tmp/to-WebOOGL.$mosaic_pid"); print CONTROL "$$\n"; close(CONTROL); 1; } sub mosaic_close_comm { # Get rid of all control and communication files. # Kill off Mosaic if we started it. if ($mosaic_pid != 0) { unlink("/tmp/Mosaic.$mosaic_pid"); unlink("/tmp/to-WebOOGL.$mosaic_pid"); unlink("/tmp/WebOOGL.$mosaic_pid"); if($original_mosaic_pid == 0) { kill('HUP', $mosaic_pid); } } 1; } ######################################################################## # Provide fixed-width 3D vector font for labels (so far just numerals # and uppercase letters). Characters are accessed with handles equal # to themselves. ######################################################################## sub loadfont { print TOGV "(read geometry { # alphabet.vect # # A list of handles defining the uppercase letters of # the Roman alphabet in a fixed-size 32-by-64 font # (40-unit spacing is recommended). # # Read it into Geomview like this: # # (read geometry { < alphabet.vect }) # # Then you can write out uppercase strings like this: # # (geometry HI { LIST # { INST geom { : H } transform { 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 } } # { INST geom { : I } transform { 1 0 0 0 0 1 0 0 0 0 1 0 40 0 0 1 } } # }) # { LIST { define / { VECT 1 2 0 2 0 0 0 0 16 64 0}} { define : { VECT 2 4 0 2 2 0 0 16 4 0 16 24 0 16 40 0 16 60 0 }} { define . { VECT 1 2 0 2 0 16 0 0 16 8 0}} { define _ { VECT 1 2 0 2 0 0 0 0 32 0 0}} { define - { VECT 1 2 0 2 0 0 32 0 32 32 0}} { define A { VECT 2 5 0 3 2 0 0 0 0 0 16 64 0 32 0 0 8 32 0 24 32 0 } } { define B { VECT 2 12 0 7 5 0 0 0 0 0 0 64 0 16 64 0 28 56 0 28 44 0 20 36 0 0 36 0 20 36 0 32 24 0 32 8 0 24 0 0 0 0 0 } } { define C { VECT 1 8 0 8 0 32 56 0 24 64 0 8 64 0 0 56 0 0 8 0 8 0 0 24 0 0 32 8 0 } } { define D { VECT 1 7 0 7 0 0 0 0 0 64 0 20 64 0 32 52 0 32 8 0 24 0 0 0 0 0 } } { define E { VECT 2 6 0 4 2 0 0 32 64 0 0 64 0 0 0 0 32 0 0 0 36 0 24 36 0 } } { define F { VECT 2 5 0 3 2 0 0 0 0 0 0 64 0 32 64 0 0 36 0 24 36 0 } } { define G { VECT 1 10 0 10 0 32 56 0 24 64 0 8 64 0 0 56 0 0 8 0 8 0 0 24 0 0 32 8 0 32 36 0 16 36 0 } } { define H { VECT 3 6 0 2 2 2 0 0 0 0 0 0 0 64 0 32 0 0 32 64 0 0 36 0 32 36 0 } } { define I { VECT 3 6 0 2 2 2 0 0 0 4 64 0 28 64 0 16 64 0 16 0 0 4 0 0 28 0 0 } } { define J { VECT 2 8 0 2 6 0 0 12 64 0 36 64 0 28 64 0 28 8 0 20 0 0 8 0 0 0 8 0 0 20 0 } } { define K { VECT 2 5 0 2 3 0 0 0 0 0 0 64 0 24 64 0 0 36 0 32 0 0 } } { define L { VECT 1 3 0 3 0 0 64 0 0 0 0 32 0 0 } } { define M { VECT 1 5 0 5 0 0 0 0 0 64 0 16 36 0 32 64 0 32 0 0 } } { define N { VECT 1 4 0 4 0 0 0 0 0 64 0 32 0 0 32 64 0 } } { define O { VECT 1 9 0 9 0 24 0 0 8 0 0 0 8 0 0 52 0 8 64 0 24 64 0 32 52 0 32 8 0 24 0 0 } } { define P { VECT 1 7 0 7 0 0 0 0 0 64 0 16 64 0 28 56 0 28 44 0 20 36 0 0 36 0 } } { define Q { VECT 2 11 0 9 2 0 0 24 0 0 8 0 0 0 8 0 0 52 0 8 64 0 24 64 0 32 52 0 32 8 0 24 0 0 20 16 0 36 0 0 } } { define R { VECT 2 9 0 7 2 0 0 0 0 0 0 64 0 16 64 0 28 56 0 28 44 0 16 36 0 0 36 0 16 36 0 32 0 0 } } { define S { VECT 1 12 0 12 0 32 56 0 24 64 0 8 64 0 0 56 0 0 44 0 8 36 0 20 36 0 32 24 0 32 8 0 24 0 0 8 0 0 0 8 0 } } { define T { VECT 2 4 0 2 2 0 0 0 64 0 32 64 0 16 0 0 16 64 0 } } { define U { VECT 1 6 0 6 0 0 64 0 0 8 0 8 0 0 24 0 0 32 8 0 32 64 0 } } { define V { VECT 1 3 0 3 0 0 64 0 16 0 0 32 64 0 } } { define W { VECT 1 5 0 5 0 0 64 0 4 0 0 16 28 0 28 0 0 32 64 0 } } { define X { VECT 2 4 0 2 2 0 0 0 64 0 32 0 0 0 0 0 32 64 0 } } { define Y { VECT 2 5 0 2 3 0 0 0 64 0 16 36 0 32 64 0 16 36 0 16 0 0 } } { define Z { VECT 1 4 0 4 0 0 64 0 32 64 0 0 0 0 32 0 0 } } { define 0 { VECT 2 11 0 2 9 0 0 0 20 0 28 56 0 24 0 0 8 0 0 0 8 0 0 48 0 8 64 0 24 64 0 32 52 0 32 12 0 24 0 0 } } { define 1 { VECT 2 5 0 2 3 0 0 4 0 0 28 0 0 4 44 0 16 64 0 16 0 0 } } { define 2 { VECT 1 7 0 7 0 0 56 0 8 64 0 24 64 0 32 56 0 32 40 0 0 0 0 32 0 0 } } { define 3 { VECT 2 13 0 2 11 0 0 8 36 0 24 36 0 0 56 0 8 64 0 24 64 0 32 56 0 32 44 0 24 36 0 32 24 0 32 8 0 24 0 0 8 0 0 0 8 0 } } { define 4 { VECT 2 5 0 2 3 0 0 28 64 0 28 0 0 12 64 0 0 36 0 36 36 0 } } { define 5 { VECT 1 9 0 9 0 28 64 0 4 64 0 0 36 0 24 36 0 32 24 0 32 8 0 24 0 0 8 0 0 0 8 0 } } { define 6 { VECT 1 12 0 12 0 32 56 0 24 64 0 8 64 0 0 56 0 0 8 0 8 0 0 24 0 0 32 8 0 32 28 0 24 36 0 8 36 0 0 28 0 } } { define 7 { VECT 1 4 0 4 0 0 56 0 4 64 0 32 64 0 8 0 0 } } { define 8 { VECT 1 16 0 16 0 20 36 0 28 44 0 28 56 0 20 64 0 12 64 0 4 56 0 4 44 0 12 36 0 20 36 0 32 24 0 32 8 0 24 0 0 8 0 0 0 8 0 0 24 0 12 36 0 } } { define 9 { VECT 1 12 0 12 0 32 44 0 24 36 0 8 36 0 0 44 0 0 56 0 8 64 0 24 64 0 32 56 0 32 8 0 24 0 0 8 0 0 0 8 0 } } } })"; # end of Gcl command sent to Geomview } # end of Perl subroutine sub setup_controls { # since the camera is 3 units away by default $dist = 1.1; # looks reasonable $frameaspect = 20; @bar = split(/\s+/,`xdpyinfo | awk ' /dimensions/ {print $2 }'`); @screensize = split(/x/,@bar[2],2); @size = (@screensize[0],@screensize[0]/$frameaspect); @size[1] = int(@size[1]); $boxsize = $frameaspect*$dist; # correct FOV for hyp space with otherwise default camera $hypfov = .366; $eucfov = 2.2; $fov = $eucfov if (!defined($fov)); print TOGV " (progn (window default {noborder size $screensize[0] $size[1] position 0 $screensize[0] 0 $size[1]}) (camera controls { camtoworld transform { 1 0 0 0 0 1 0 0 0 0 1 0 0 0 3 1 } perspective 0 stereo 0 fov 2.2 frameaspect $frameaspect focus 3 near 0.1 far 100 }) (scene controls {appearance {linewidth 2 +edge} {LIST { : title } { : url } { : fbutton } { : abutton } }}) (window default { position 0 300 0 300}) ) "; $fbutton="{QUAD -$boxsize -$dist 0 0 -$dist 0 0 $dist 0 -$boxsize $dist 0} "; $abutton = "{QUAD $boxsize -$dist 0 0 -$dist 0 0 $dist 0 $boxsize $dist 0} "; $redcol = " appearance {material *diffuse 1 0 0 } "; $bluecol = " appearance {material *diffuse .2 .2 1 } "; $graycol = " appearance {material *diffuse .4 .4 .4 } "; $flashcol = " appearance {material *diffuse .7 .7 .7 } "; $followon = "(read geometry {define fbutton {$redcol $fbutton}})"; $followoff = "(read geometry {define fbutton {$graycol $fbutton}})"; $abouton = "(read geometry {define abutton {$bluecol $abutton}})"; $aboutoff = "(read geometry {define abutton {$graycol $abutton}})"; $flash = "(read geometry {define abutton {$flashcol $abutton}})"; if ($actionmode eq "about") { print TOGV "$abouton $followoff\n"; } else { print TOGV "$aboutoff $followon\n"; } } sub genlabels { local($str, $tier) = @_; local (@str); # alphabet is 32x64 local ($spacing) = $dist*0.5; local ($labelscale) = $dist*.5/64; # @labeladj[0] = -$boxsize+($frameaspect*.05); @labeladj[0] = +$boxsize-($frameaspect*.05); @labeladj[1] = $dist*.25 - $dist*.9; @labeladj[2] = .01; local($x, $y, $z) = @labeladj; if ($tier eq "title") {$y += $dist;} print "(read geometry { define $tier {LIST\n"; $str =~ tr/a-z/A-Z/; @str = split(//,$str); @str = reverse(@str); for ($i = 0; $i <= $#str; $i++) { if (@str[$i] =~ /\w/ || @str[$i] =~ /[\/:_.-]/) { printf " \t{INST geom {: %s } transform { $labelscale 0 0 0 0 $labelscale 0 0 0 0 $labelscale 0 $x $y $z 1 }}\n", @str[$i]; } $x -= $spacing; } print "}})\n"; } 1;