#!/usr/bin/perl -w    # You may need to change path of perl here.

########################################################################
# blochviewer: A 3D vector graphics viewer parsing output from an MRI
#              simulator illustrating the physics expressed in the Bloch 
#              equations. The simulator and the viewer is available at 
#                   http://www.drcmr.dk/bloch
#              where info regarding installation and troubleshooting 
#              can also be found. Normal configuration is done by editing
#              the beginning of the main program (search below for "settings").
#
#              The viewer depends on the perl OpenGL (POGL) module.
#              The graphics can be saved if the OpenGL::Image module 
#              is installed.
#
#              There is room for improvement. Please let me know of yours.
#
# Copyright 2008 Lars G. Hanson <larsh#drcmr.dk>    (# -> @)
# The program is distributed under the terms of the GNU General Public License.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Changes:
#
# Revision 3.0 2008/5/1 larsh 
# Initial release version numbered to match corresponding simulator
# version.  The code was inspired by various POGL demos available on
# the web, most notably "fun" and "hello_teapot". The program works
# but not in the most beautiful or efficient way. Tested on Windows 
# and Linux (Redhat and Ubuntu) with POGL v. 0.54 and 0.56.
#
########################################################################

use strict;

# If you get a floor, but no magnetization vector, you may need to download
# POGL and change the following paths to reflect the position of the 
# mentioned directories (I expect a better solution exists, but I do not
# know it. You?).
BEGIN{ unshift(@INC,"OpenGL-0.56/blib"); }  # in case OpenGL is built but not installed
BEGIN{ unshift(@INC,"OpenGL-0.56/blib/arch"); } # 5.002 gamma needs this
BEGIN{ unshift(@INC,"OpenGL-0.56/blib/lib"); } # 5.002 gamma needs this

my $ReqCheckOnly;
foreach (@ARGV) { # Options:
    (/^-c/ || /^--reqcheckonly/) && ($ReqCheckOnly = 1); 
    # -c or --reqcheckonly: Check if minimal requirements are fulfilled and
    # exit. This is used by the Bloch simulator, for a quick check of reqs.
}

eval("use OpenGL qw/:old :glufunctions :gluconstants :glutfunctions :glutconstants/"); 
# I have no idea why the line above needs to be 
# repeated below without eval, which causes a compile-time error, 
# instead of the desired run-time detection of a missing module.
my $hasOpenGL = !$@;
use OpenGL qw/:old :glufunctions :gluconstants :glutfunctions :glutconstants/; 
# Would like to get rid of the line above, but constants are then unknown.

eval("use OpenGL::Image");
(my $hasImage = !$@ ) || print("The module OpenGL::Image was not found. Viewer may work, but graphics cannot be recorded and saved to disk.\n") ;

eval("use File::Basename");
my $hasBasename = !$@;

eval("use Archive::Tar");
my $hasTar = !$@;

if ($ReqCheckOnly || (! $hasOpenGL)) {
    if ($hasOpenGL) {
	print "\nModule OpenGL found. Minimal requirements appear to be fulfilled. Good!\n";
	if ($hasImage) {
	    print "Module OpenGL::Image found. Animations can be saved.\n";
	    if ($hasTar) {
		print "Module Archive::Tar found. Saved animation frames can be bundled and compressed.\n";
	    } else {
		print "Module Archive::Tar not found. Saved animation frames will not be bundled or compressed. Not crucial.\n";
	    }
	} else {
	    print "Module OpenGL::Image not found. Animations cannot be saved. Not crucial.\n";
	}
    } else {
	print "Module OpenGL is needed but not found. See http://www.drcmr.dk/bloch for hints.\n";
    }
    $ReqCheckOnly && exit(1-$hasOpenGL);
}

#----------- settings ---------------
# You may want to adapt the parameters below.

my $GraphFile = "";# Path to file/pipe with output from Bloch Simulator. 
                   # Reading from STDIN/pipe, if empty.
my $ShowURL = "http://www.drcmr.dk/bloch"; # Clear string to show nothing. 
my $Iconic = 0; # If set, enter iconic mode used for animations on the DRCMR homepage.
my $width;
my $height; # Initial window width and height.
if ($Iconic) {
    $width = 161;  
    $height = 147; 
} else {
    $width = 500;  
    $height = 490; 
}
my $ViewHelp = 1-$Iconic; # Show help text initially.
my $Nsig = 4;     # Maximum number of signals kept track of.
my $NvalHist = 100; # Length of signal history.
my $Scale = 15;     # Graphics scaling. Increasing will cause clipping.
my $light = 1;      # Enable light.
my $FixedFloor = 0; # Keep floor fixed independent of frame.
my $ViewSignal = 1; # Show signal in sliding window.
my $ViewOutStr = 1; # Show event messages.
my $Archive = $hasTar; # Archives and compresses saved frames, if possible.
my $ViewAngleMax = 60; # Polar viewing angle.
my @TransitionFunc = (0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1);#Gradual view angle change 
my $FirstFrame = "f0001";

# You probably dont want to change these:
my $FrameOfRefPhase=0;
my $LastPhase=0;
my $SceneOne = 1;
my $Cframe = $FirstFrame;
my $LastFrameFile = '';
my $TransitionPtr = $#TransitionFunc;
my $TransitionInc = 0;
my @SigHist; 
my $OutStr;
my @Sigs;
my @PriorSigs;
my $SaveFrameFile; 
my $tar;
my $ViewAngle;
my @SigColor;


sub PrintString {
  my ($font, $str) = @_; 
  $str =~ s/!D(.*)!N/$1/; #remove IDL format codes.
  my @c = split '', $str; 
  for(@c) {
    glutBitmapCharacter($font, ord $_);
  }
} 

sub BitmapLength { # Implementation of glutBitmapLength
  my ($font, $str) = @_; 
  $str =~ s/!D(.*)!N/$1/; #remove IDL format codes.
  my @c = split '', $str; 
  my $BitmapLength = 0;
  for(@c) {
    $BitmapLength += glutBitmapWidth($font, ord $_);
  }
  return $BitmapLength;
} 

sub CloseTar {
    my ($tar,$File) = @_;
    my @Content = $tar->list_files();
    my $Nfiles = $#Content+1;
    print "Moving $Nfiles graphics files to $File.tar.gz...\n";
    if ($hasBasename) {
	my ($Basename,$Dir) = fileparse($File);
	chdir($Dir);
    }
    $tar->write("$File.tar.gz", 1, "frames");
    unlink(@Content);
} 

sub ResizeScene {
    ($width, $height) = @_;
    glViewport(0, 0, $width, $height);
 }

sub SaveFrame {
    my($w,$h,$file) = @_;
    if ($hasImage) {
        my $frame = new OpenGL::Image(width=>$w, height=>$h);
        my($fmt,$size) = $frame->Get('gl_format','gl_type');
        OpenGL::glReadPixels_c( 0, 0, $w, $h, $fmt, $size, $frame->Ptr() );
        $frame->Save($file);
    } else {
        print "Need OpenGL::Image and ImageMagick 6.3.5 or newer for file capture!\n";
    }
}

sub KeyPressed {
    my $key = shift;
    my $c = lc chr $key;
    
    ($key == 27 or $c eq 'q') && die("Quit was chosen.\n");
    
    if ($c eq "v") { # Initiate viewing angle change, if not already changing.
	if ($TransitionInc == 0) {
	    $TransitionInc = 2*($TransitionPtr == 0)-1; 
	}
	return; 
    }

    if ($c eq "h") {
	$ViewHelp = 1-$ViewHelp;
	return; 
    }

    if ($c eq "f") {
	$FixedFloor = 1-$FixedFloor;
	return; 
    }

    if ($c eq "s") {
	$ViewSignal = 1-$ViewSignal;
	return; 
    }

    print "No action for key $c\n";
} # KeyPressed

sub InitFloor{
my ($ListId, $R1, $G1, $B1, $R2, $G2, $B2) = @_;
# Initialize list that creates a floor like POGL demo "fun".
	glNewList($ListId,GL_COMPILE);
	glPushAttrib(GL_ALL_ATTRIB_BITS);
	glDisable(GL_LIGHTING);
	foreach my $i (0..9) {
	    foreach my $j (0..9) {
	      my $b = (1-sqrt(($i-4.5)*($i-4.5)+($j-4.5)*($j-4.5))/6.4)/255;
	      my $OddSum = ($i + $j)%2;
	      glColor3f($b*($OddSum?$R1:$R2),
			$b*($OddSum?$G1:$G2),
			$b*($OddSum?$B1:$B2)); 
	      glBegin(GL_POLYGON);
	      glVertex3f( (0+$i-5)*10/$Scale ,  (0+$j-5) *10/$Scale,  -1.1);
	      glVertex3f( (1+$i-5)*10/$Scale ,  (0+$j-5) *10/$Scale,  -1.1);
	      glVertex3f( (1+$i-5)*10/$Scale ,  (1+$j-5) *10/$Scale,  -1.1);
	      glVertex3f( (0+$i-5)*10/$Scale ,  (1+$j-5) *10/$Scale,  -1.1);
	    glEnd();
	  }
	 }
	glPopAttrib();
	glEndList();
} #InitFloor

sub DrawFromPipe { # Parses stream of graphics commands from Bloch simulator.
    my @mat_specular = ( 0.0, 0.0, 0.0, 1.0 );
    my $SigLineFound = 0;
    my @mat_diffuse;
    my $Projection;
    $OutStr = '';
  LINE:
    while (<GRAPHFILE>) {
#	warn "$_\n";   #Uncomment to copy stream of commands to STDERR.

	/^#/ && next LINE; # Ignore comments

	/^flush/ && last LINE; # Finish rendering and output screen.
	
	if (/^framephase\s+([-\d\.e]+)/) { # Set frame-of-reference phase.
	    $FrameOfRefPhase = $1;
	    next LINE;
	}
	
	if (/^saveframe\s+(.+)/) { # Save scene in image file when rendered.
	    $SaveFrameFile = $1;
	    $SaveFrameFile =~ s/\....\s*$//; # remove any filename suffix.
	    next LINE;
	}
	if (/keypress\s+(\w)/) { # Parse keypress caught by simulator.
	    KeyPressed(ord($1));
	    next LINE;
	}
	if (/^outstr\s*(.*)/) { # Event messages from simulator.
	    $OutStr = $1;
	  SWITCH: {
	      #Text translations to exploit space available for event messages.
	      ($OutStr =~ s/^(\d+[xy])/RF pulse: $1/) && last SWITCH;
	      ($OutStr =~ s/^Spoil/Spoiling/) && last SWITCH;
	      if (($width gt 300) || (! $ViewSignal)) {
		  ($OutStr =~ s/^Rotating/Rotating frame/) && last SWITCH;
		  ($OutStr =~ s/^Stationary/Stationary frame/) && last SWITCH;
	      }
	  }
	    next LINE;
	}
	
	if (/^fixedfloor\s+(\d)/) { # Fix floor independent of frame-of-ref
	    $FixedFloor = $1;
	    next LINE;
	}
	if (/^signal\s+(\d+)\s+([-\d\.e]+)\s+(\d+)\s+(\d+)\s+(\d+)/) { 
	    # Update signal history (sliding window).
	    if (! $SigLineFound) { 
		# Prepare list of signals that are still active.
		@PriorSigs = @Sigs;
		@Sigs = ();
		$SigLineFound = 1;
	    }
	    my $Csig = $1;
	    if ($Csig < $Nsig) {
		shift(@{$SigHist[$Csig]}); ; # Forget the far past.
		push(@{$SigHist[$Csig]},$2); # Remember the present signal.
		push(@Sigs,$Csig); # Add to list of active signals.
		$SigColor[$Csig] = [$3,$4,$5];
	    }
	    next LINE;
	}
	
	if (/^done/) { # Termination command sent from simulator.
	    # Write archive if it contains more than one file.
	    $Archive && ($Cframe ne $FirstFrame) && ($Cframe ne ++$FirstFrame) && CloseTar($tar,$LastFrameFile);
	    die "Bloch simulator terminated.\n";
	}
	if (/^plots/) { # Parse vector graphics command from simulator.
	  PLOTLINE:
	    while (<GRAPHFILE>) {
		/^endplots/ && last;
		if (/^color\s+(\d+)\s+(\d+)\s+(\d+)/) {
		    @mat_diffuse  = ( $1/255, $2/255, $3/255, 1.0 );
		    $Projection = ($1 == 0) && ($2 == 0) && ($3 == 0);
		    next PLOTLINE;
		}
		if (/^xyz/) {
		    my $First = 1;
		    my ($x0, $y0, $z0, $x1, $y1, $z1);
		  XYZLINE:
		    while (<GRAPHFILE>){
			/^endxyz/ && last XYZLINE;
			if (/^\s*([-\d\.e]+)\s+([-\d\.e]+)\s+([-\d\.e]+)/ && $First) {
			    $x0=$1 ; $y0=$2 ; $z0=$3;
			    $First = 0;
			} else {
			    $x1=$1 ; $y1=$2 ; $z1=$3;
			    my $length = sqrt(($x1-$x0)**2 + ($y1-$y0)**2 + ($z1-$z0)**2);
			    my $quad = OpenGL::gluNewQuadric();
			    glPushMatrix();
			    
			    glMaterialfv(GL_FRONT, GL_DIFFUSE, pack("f4",@mat_diffuse));
			    glMaterialfv(GL_FRONT, GL_SPECULAR, pack("f4",@mat_specular));
			    glMaterialf(GL_FRONT, GL_SHININESS, 10);
			    
			    
			    glTranslatef($x0, $y0, $z0);
			    glRotatef(180,($x1-$x0)/2,
				      ($y1-$y0)/2,
				      ($length+($z1-$z0))/2);
			    gluQuadricOrientation($quad, GLU_OUTSIDE);
			    
## Draw vector unless it is a shadows seen from above.
			    if (! ($Projection && ($ViewAngle == 0))) {
				gluCylinder($quad,0.3/$Scale,0.3/$Scale,
					    $length,$Projection?6:18,1); }
			    glPopMatrix();
			    $x0=$x1; $y0=$y1; $z0=$z1;
			}
		    }
		    next PLOTLINE;
		}
	    }
	    next LINE;
	}
    }
} # DrawFromPipe


sub RenderScene { 
# Main scene drawing procedure called from GlutMainLoop.
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glLoadIdentity ();
    glEnable(GL_DEPTH_TEST);
    glTranslatef (0.0, 0.0, -30.0/$Scale);    
    
    glPushMatrix();
    
    if ($TransitionInc) { # If viewing angle transition is ongoing....
	$TransitionPtr += $TransitionInc; # Step viewing angle.
	($TransitionPtr % $#TransitionFunc) || ($TransitionInc=0);# Finished!
    }
    $ViewAngle = $ViewAngleMax*$TransitionFunc[$TransitionPtr];
    glRotatef(-$ViewAngle, 1.0,0.0,0.0);  # Set polar viewing angle.
    ($TransitionPtr != $#TransitionFunc) && # Set azimuthal viewing angle.
	glRotatef(-40+40*$TransitionFunc[$TransitionPtr], 0.0,0.0,1.0);  
    
    glRotatef(40, 0.0,0.0,1.0);	
    
# Rotate scene
    glPushMatrix();
    my $Phase;
    if (! $FixedFloor) {
	$Phase = $FrameOfRefPhase;
	$LastPhase = $FrameOfRefPhase;
    } else {
	$Phase=$LastPhase;
    }
    glRotatef(-$Phase*180/3.1415927, 0.0,0.0,1.0);
    glCallList($FixedFloor+1);
    glPopMatrix();
    
    $SceneOne || DrawFromPipe(); # Parse commands from simulator.
    glPopMatrix();
    
# Draw signal history
    if ($ViewSignal && ! $SceneOne) { 
	glPushMatrix();
	glPushAttrib(GL_ALL_ATTRIB_BITS);
	glDisable(GL_LIGHTING);
	
	glLineWidth(2);
	my $OffsetX = 0.55; 
	my $OffsetY = 0.75;
	my $PixelShift = 0.01;
	foreach my $Csig (@Sigs) {
	    glColor3f($SigColor[$Csig][0]/255,
		      $SigColor[$Csig][1]/255,
		      $SigColor[$Csig][2]/255);
	    glBegin(GL_LINE_STRIP);
	    foreach (1..$NvalHist) {
		glVertex3f(($_-1)/$NvalHist+$OffsetX,
			   $SigHist[$Csig][$_-1]/1.5+$OffsetY,0);
	    }
	    glEnd();
	}
	
# Draw frame for coordinate system:
	glTranslatef(-$PixelShift*0.75,-$PixelShift,0);
	glBegin(GL_LINE_STRIP); 
	glColor3f(0.5,0.5,0.5);
	glVertex3f($OffsetX,$OffsetY,0);
	glVertex3f($OffsetX+1,$OffsetY,0);
	glVertex3f($OffsetX+1,$OffsetY+0.8,0);
	glVertex3f($OffsetX,$OffsetY+0.8,0);
	glVertex3f($OffsetX,$OffsetY,0);
	glEnd();

# Clear history of signals that no longer are relevant.
	foreach my $Csig (@PriorSigs) {
	    my $Found = 0;
	    foreach my $Csig2 (@Sigs) {
		$Found = ($Found || ($Csig == $Csig2)); }
	    if (! $Found) {
		for (0..$NvalHist) { 
		    $SigHist[$Csig][$_]=0; ; #Clear historic signal values.
		}
	    }
	}
	glPopAttrib();
	glPopMatrix();
    }	
    
#Axis text
    my $AxisFont;
    glPushMatrix();
    glPushAttrib(GL_ALL_ATTRIB_BITS);
    glOrtho(0,$width,0,$height,-1.0,1.0);
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_LIGHTING);
    glDisable(GL_DEPTH_TEST);
    if ($ViewSignal && ! $SceneOne) {
	glColor4f(0.6,1.0,0.6,.75);
	if ($width>600) {
	    $AxisFont = GLUT_BITMAP_HELVETICA_18;
	} else {
	    $AxisFont = GLUT_BITMAP_TIMES_ROMAN_10;
	}
	glRasterPos2i(1.05*$width-BitmapLength($AxisFont,'Time'),0.8*$height);
	PrintString($AxisFont,"Time");
	
	glRasterPos2i($width*0.78-2*BitmapLength($AxisFont,'Signal'),$height*1.23);
	PrintString($AxisFont,'Signal');
    }
    
# Print event messages
    glColor3f(1,1,1);
    glRasterPos2i(-$width/6,1.1*$height);
    my $OutStrFont = ($width gt 200)? GLUT_BITMAP_TIMES_ROMAN_24:GLUT_BITMAP_HELVETICA_18;
    $ViewOutStr && $OutStr && PrintString($OutStrFont,$OutStr);
    
# Print help texts 
    my $HelpFont;
    my $FontHeight;
    if (! $SceneOne) {
	$HelpFont = GLUT_BITMAP_HELVETICA_12;
	$FontHeight = 12;
	if ($ViewHelp) {
	    glRasterPos2i(-$width*0.2,+1*$FontHeight);
	    PrintString($HelpFont,"h: Toggles this overview of keys.");
	    glRasterPos2i(-$width*0.2,-1*$FontHeight);
	    PrintString($HelpFont,"v: Toggles viewing angle.");
	    glRasterPos2i(-$width*0.2,-3*$FontHeight);
	    PrintString($HelpFont,"f: Toggles floor rotation.");
	    glRasterPos2i(-$width*0.2,-5*$FontHeight);
	    PrintString($HelpFont,"s: Toggles signal viewing.");
	    glRasterPos2i(-$width*0.2,-7*$FontHeight);
	    PrintString($HelpFont,"q: Quits.");
	} 
	if ($ShowURL) {
	    if ($Iconic) {
		glRasterPos2i(-BitmapLength($HelpFont,$ShowURL)+0.7*$width,
			      -1.8*$FontHeight);
	    } else {	    
		glRasterPos2i(-BitmapLength($HelpFont,$ShowURL)+$width,
			      -7*$FontHeight);
	    }
	    PrintString($HelpFont,$ShowURL);	
	}
    } else {
	# Welcome screen
	$HelpFont = GLUT_BITMAP_TIMES_ROMAN_24;
	$FontHeight = 24;
	my $str = "Viewer for Bloch simulator";
	glRasterPos2i(0.6*$width-BitmapLength(GLUT_BITMAP_TIMES_ROMAN_24,$str),0);
	PrintString(GLUT_BITMAP_TIMES_ROMAN_24,$str);

	$str = "http://www.drcmr.dk/bloch";
	glRasterPos2i(0.6*$width-BitmapLength(GLUT_BITMAP_HELVETICA_18,$str),-2*$FontHeight);
	PrintString(GLUT_BITMAP_HELVETICA_18,$str);

	$str = "Action begins when graphics commands arrive from simulator.";
	glRasterPos2i(0.6*$width-BitmapLength(GLUT_BITMAP_HELVETICA_12,$str),0-4*$FontHeight);
	PrintString(GLUT_BITMAP_HELVETICA_12,$str);

	if ($GraphFile) {
	    if (-r $GraphFile) {
		$str = "Reading graphics command from file or pipe $GraphFile..."; 
	    } else {
		$str = "File or pipe $GraphFile is not readable. Reading from STDIN instead..."; }
	} else {
	    $str = "Awaiting graphics commands from STDIN..."; 
	}
	
	glRasterPos2i(0.6*$width-BitmapLength(GLUT_BITMAP_HELVETICA_12,$str),0-5*$FontHeight);
	PrintString(GLUT_BITMAP_HELVETICA_12,$str);
    }
    
    glPopAttrib();
    glPopMatrix();
    
    glutSwapBuffers();
    
    if ($SceneOne) {
# Only open pipe or file with output from the Bloch simulator after 
# the first scene has been rendered, as reading may block.
	if (-r $GraphFile) {
	    open(GRAPHFILE, "< $GraphFile") || die "Cant open file or pipe $GraphFile\n";
	} else {
	    *GRAPHFILE = *STDIN;
	}
	$SceneOne = 0;
    }

#Rendering finished. Save result in file, if wanted.
    if ($SaveFrameFile) { 
	if ($SaveFrameFile ne $LastFrameFile) { # New recording has started
	    $Archive && ($Cframe ne $FirstFrame) && CloseTar($tar,$LastFrameFile);
	    $Cframe = $FirstFrame; 
	} 
	SaveFrame($width,$height,$SaveFrameFile."_$Cframe.tga");

# Add saved frame to tar-archive.
	if ($Archive) {
	    ($Cframe eq $FirstFrame) && 
		($tar = Archive::Tar->new());
	    my $FileToAdd;
	    if ($hasBasename) {
		my ($Basename,$Dir) = fileparse($SaveFrameFile);
		$FileToAdd = $Basename;
		chdir($Dir);
	    } else {
		$FileToAdd = $SaveFrameFile;
	    }
	    $tar->add_files($FileToAdd."_$Cframe.tga");
	}
	$Cframe++;
	$LastFrameFile = $SaveFrameFile;
	$SaveFrameFile='';
    }
} # RenderScene

#---------main action starts here---------

# Initialize signal history.
foreach my $Csig (0..$Nsig-1) {
    $SigHist[$Csig] = [ 0..($NvalHist-1) ]; # creates $Sighist[i][j]
    foreach (0..$NvalHist-1) { 
	$SigHist[$Csig][$_]=0; ; #Initialize signal history with zeros.
    }
}

# Initialize GLUT
eval {glutInit(); 1} or die "This viewer requires GLUT.\n";
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH | GLUT_ALPHA);
glutInitWindowSize($width, $height);
my $Window_ID = glutCreateWindow( "Bloch viewer v. 3.0" );

glutDisplayFunc(\&RenderScene);
glutIdleFunc(\&RenderScene);
glutKeyboardFunc(\&KeyPressed);
glutReshapeFunc(\&ResizeScene);

glShadeModel (OpenGL::GL_FLAT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glFrustum (-0.8, 0.8, -0.8, 0.8, 0.98, 500.0/$Scale); 
glMatrixMode(GL_MODELVIEW);

InitFloor(1, 176,171,169, 23,164,200); # gray/blue floor (ListID, RGBx2)
#InitFloor(1, 128,178,0, 128,0,0); # green/red floor
InitFloor(2, 176,171,169, 201,12,12); # gray/red floor

glColor3f(1,0,0);
glClearColor(0,0,0.1,1); # Background color

if ($light) {   # Set light source
    my @light_position = ( -3, 0, 3.0, 0.0 );
    my @light_white  = ( 1.0, 1.0, 1.0, 1.0 );
    my @light_ambient  = ( 0.5, 0.5, 0.5, 1.0 );
    
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, pack("f4",@light_ambient));
    
    glLightfv(GL_LIGHT0, GL_AMBIENT, pack("f4", @light_white));
    glLightfv(GL_LIGHT0, GL_POSITION, pack("f4",@light_position));
    
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
}

# Transfer control to GLUT:
glutMainLoop();  # Never returns.
