#!/usr/bin/perl
#
# Name:		GLgraph
#
# Version:	0.2.0
#
# License:	GPL
#
# 2002/06/27	Jonas Jermann <jjermann@gmx.ch>
#               David Gunzinger <david@zhadum.ch>
#
#
################################################################################
#
# M O D U L E S

use OpenGL ":old",":glutfunctions",":gluconstants",":functions";
use strict; no strict "subs";
use Math::Trig;
use Getopt::Long;
#use Time::HiRes qw (gettimeofday tv_interval usleep); # TODO
#use warnings; # DEBUG
#use Devel::AutoProfiler;

################################################################################
#
# M A I N

# Variables
our (%options,@geometry,@tri2d,@tri3d,@yreal,$PI,$n,$z,$t,$time,$zsize,$tsize);

options();
commandline();
init();
reset_all(2);
idle();
glutMainLoop;


################################################################################
#
# S U B

# VERSION
sub version {
    my $version_string="0.2.0";
    print "GLgraph version $version_string\n";
    exit;
}

# USAGE
sub usage {
print STDERR <<EOF;

Usage: glgraph [options] 'function1' 'function2' ...

options:
  function         Function: f(x,z,t)=function
  -(no)fs          Fullscreen                                (DEFAULT = ON)
  -geometry        Window Geometry                           (DEFAULT = 1024x768+0+0)
  -wid             Window Id                                 (DEFAULT = "graph window")
  -nograph         Don't draw just calculate
  -2d              2D Graph
  -wire            3D Graph: Wireframe
  -3d              3D Graph: Solid                           (DEFAULT)
  -(no)time        Time Graph (notime is BUGGY)              (DEFAULT = ON)
  -(no)colors      Color Mode (0=nocolors, 1:mode1, 2:mode2) (DEFAULT =   1)
  -xmin            Beginning of x interval (Window)          (DEFAULT =  -5)
  -xmax            Ending of x interval (Window)             (DEFAULT =   5)
  -ymin            Beginning of y interval (Window)          (DEFAULT =  -5)
  -ymax            Ending of y interval (Window)             (DEFAULT =   5)
  -zmin            Beginning of z interval (Window)          (DEFAULT =  -5)
  -zmax            Ending of z interval (Window)             (DEFAULT =   5)
  -tmin            Beginning of t (time) interval (Window)   (DEFAULT =  -4)
  -tmax            Ending of t (time) interval (Window)      (DEFAULT =   4)
  -xres            Resolution of x axis (relevant dots)      (DEFAULT =  40)
  -zres            Resolution of z axis (relevant dots)      (DEFAULT =  50)
  -xzres           Space Resolution (xres and zres)
  -tres            Resolution of time "axis" (frames)        (DEFAULT =  50)
  -cfact           Interval of color function                (DEFAULT = 0.8)
  -cconst          Displace colors (0-1)                     (DEFAULT = 0)
  -h, --help       Display this help message
  -v, --version    Display GLgraph's version number

keys:
  q                Quit
  _                Reset
  m                Screenshot (doesn't work properly)
  w/a/s/d          Rotation
  ü/ö/ä/\$          Translation
  e and r          Scaling
  b and n          Change functions
  1 and 2          Change graphic mode
  3 and 4          Change color-range
  c                Toggle color-mode
  t                Toggle time-mode
  y and x          Seeking
  z and u          Change space resolution
  h and j          Change time resolution

EOF
    exit 1;
}

# COMMAND LINE
sub commandline {
    GetOptions(
        "fullscreen|fs"      => \$options{fullscreen},
        "nofullscreen|nofs"  => sub { $options{fullscreen}=0; },
        "geometry=s"         => sub { @geometry=split(/[x+]/,"$_[1]") },
        "wid=s"              => \$options{wid},
       	"nograph"            => sub { $options{mode}=0 },
       	"2d"                 => sub { $options{mode}=1 },
	"wire|3dwire"        => sub { $options{mode}=2 },
	"3d|3dsolid"         => sub { $options{mode}=3 },
	"time"               => sub { $options{time}=1 },
	"notime"             => sub { $options{time}=0 },
	"colors=i"           => \$options{color},
	"nocolors"           => sub { $options{color}=0 },
	"xmin=f"             => \$options{xmin},
        "xmax=f"             => \$options{xmax},
        "ymin=f"             => \$options{ymin},
        "ymax=f"             => \$options{ymax},
        "zmin=f"             => \$options{zmin},
        "zmax=f"             => \$options{zmax},
        "tmin=f"             => \$options{tmin},
        "xres=i"             => \$options{xres},
        "zres=i"             => \$options{zres},
        "xzres=i"            => sub { $options{xres}=$_[1]; $options{zres}=$_[1]; },
	"tres=i"             => \$options{tres},
        "fps=f"              => \$options{fps},
        "cfact=f"            => \$options{cfact},
        "cconst=f"           => \$options{cconst},
        "o=s"                => \$options{image_name},
        "help|h"             => \&usage,
        "version|v"          => \&version,
    ) || usage();

    # Add command line functions BUGGY
    @yreal = (@ARGV, @yreal);
    for (@yreal) { $_ =~ s/(?<!\w)([xzt])(?!\w)/\$$1$2/g; }
    for (@yreal) { $_ =~ s/PI/\$PI/g; }
}

# DEFAULT OPTIONS
sub options {
    # Init variables
    $options{fullscreen} = 1;
    @geometry = (1024,768,0,0);
    $options{wid} = "graph window";
    $options{image_name} = "image";
    $n = 0;
    $PI = 3.415926535898;

    # Mode:
    #   0   no graph
    #   1   2d
    #   2   3d wire
    #   3   3d solid
    # Time:
    #   0   stable (no time)
    #   1   time-function forward
    #   2   time-function backward
    # Color:
    #   0   no colours
    #   1   iterative color-mode 1
    #   2   absolute color-mode 2 (pfy)
    # View: TODO
    #   0   orthographic viewing
    #   1   perspective viewing
    #   2   stereo viewing
    #   3   2nd way of stereo viewing
    
    $options{mode}   = 3;
    $options{time}   = 1;
    $options{color}  = 1;
    #$options{view}   = 0;

    # Window
    $options{xmin}   = -5;
    $options{xmax}   = 5;
    $options{ymin}   = -5;
    $options{ymax}   = 4;
    $options{zmin}   = -5;
    $options{zmax}   = 5;
    $options{tmin}   = -4;
    $options{tmax}   = 4;

    # Axis and misc
    $options{xres}   = 40;
    $options{zres}   = 50;
    $options{tres}   = 50;
    $options{fps}    = 20; #TODO
    $options{cfact}  = 0.8;
    $options{cconst} = 0;

    # Functions
    $yreal[0] = '2*cos(t*sqrt(z*z+x*x)-2*atan(x/z))';
    $yreal[1] = '(cos(t*sqrt(z*z+x*x))-sin(t*sqrt(z*z+x*x)))*exp(-sqrt(z*z+x*x))*5';
    $yreal[2] = '0.1*z*sin(t*(z-x))-x*cos(z*x-t)';
    $yreal[3] = 'sqrt(x*x*x)-t*z*cos(x)';
    $yreal[4] = 'sin(x*x+z*z+t*t)';
}



# INIT
sub init {
    # Glut init
#    $time = [gettimeofday];
    glutInit();
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize($geometry[0],$geometry[1]);
    glutInitWindowPosition($geometry[2],$geometry[3]);
    glutCreateWindow($options{wid});
    if ($options{fullscreen}) { glutFullScreen(); }

    # Functions
    glutKeyboardFunc(\&keyboard);
    glutDisplayFunc(\&display);
    glutReshapeFunc(\&resize);
    glutIdleFunc(\&idle);

    glClearColor(0,0,0,0);
    glMatrixMode(GL_PROJECTION);
}    



# COLOR
sub scolor { 
    if ($options{color} == 1) { 
        glColor3f(0.8*sin($_[0]*$options{cfact}+2*$options{cconst}*$PI),0.8*sin($_[0]*$options{cfact}+2*$PI/3+2*$options{cconst}*$PI),0.8*sin($_[0]*$options{cfact}+4*$PI/3+2*$options{cconst}*$PI));
    }
    elsif ($options{color} == 2) {
	my ($r,$b,$g,$a);
	$r=$b=$g=0; 
	$a=(($_[0]-$options{ymin})/($options{ymax}-$options{ymin})*$options{cfact});

	$r=$a*3;
	$r=1 if ($r > 1);

	$g=($a*3-1) if ((($a*3-1) > 0));
	$g=1 if ($g > 1);

	$b=($a*3-2) if ((($a*3-2) > 0));
	$b=1 if ($b > 1);

	glColor3f($r,$g,$b);
    }
}


# Resize: TODO
sub resize{
    ($options{width},$options{height})=@_;
    reset_all(2);
}


# RESET
sub reset_all {
    # Variables (Space)
    $z = $options{zmin};
    $zsize = 0;
    
    if ($_[0] >= 1) {
        # Variables (Time)
        $t = $options{tmin};
        $tsize = 0;
        
        if ($_[0] >=2) {
            # GL (Window)
            glLoadIdentity();
            glFlush();
            glOrtho($options{xmin},$options{xmax},$options{ymin},$options{ymax},$options{zmin},$options{zmax});
            glRotatef(-50, 5, -0.5, 0.0);
            glScalef(0.8,0.8,0.8); 
        }
    }
}



# IDLE
sub idle {

#   if (tv_interval($time)<(1/50)){
#       print "sleep ",tv_interval($time)," \n";
#       usleep (1/($options{fps}*5));
#       return;
#   }

    if ($zsize < $options{zres}) {
        if ($options{mode} == 1) {
            idle_2d();
            display();
        }
        if ($options{mode} >= 2) {
    	    idle_3d();
    	    if ($zsize == $options{zres}) {display();}
    	}
    }

    elsif (($options{time} == 1) and ($tsize < ($options{tres}-1))) { reset_all(0); $t += ($options{tmax}-$options{tmin})/($options{tres}-1); $tsize++; }
    elsif (($options{time} == 2) and ($tsize > 0)) { reset_all(0); $t -= ($options{tmax}-$options{tmin})/($options{tres}-1); $tsize--; }
    elsif ($options{time} == 1) { $options{time}=2; }
    elsif ($options{time} == 2) { $options{time}=1; }
    
#   $time = [gettimeofday];
#   usleep (0.0001) if ($options{time} == 0);

}
    


# MAIN DISPLAY (FORCE !!!!!)
sub display {
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glColor3f(0.8,0.8,0.8);
    
    if    ($options{mode} == 1) { display_2d(); }
    elsif ($options{mode} == 2) { display_3dwire(); }
    elsif ($options{mode} == 3) { display_3d(); }

    glFlush();
    glutSwapBuffers();    
}


# REDRAW: NOT WORKING YET
sub redraw {
	$zsize=0;
        if ($options{mode} == 1) {
            idle_2d();
            display();
        }
        if ($options{mode} >= 2) {
    	    while ($zsize != $options{zres}){ idle_3d();}
    	    display();
    	}
}

# KEYBOARD
sub keyboard {
    my ($key) = @_; 
    $key = chr($key);
    
    # Misc
    if   ($key=~/q/i){ exit(); }
    elsif($key=~/m/i){ screenshot(); }
    elsif($key=~/3/i){ $options{cfact}=$options{cfact}*0.98; }
    elsif($key=~/4/i){ $options{cfact}=$options{cfact}*1.02; }
    elsif($key=~/_/i){ reset_all(2); }

    # Function change
    elsif($key=~/n/i){ $n++; reset_all(0); }
    elsif(($key=~/b/i) and ($n > 0)){ $n--; reset_all(0); }

    # Mode change
    elsif(($key=~/1/i) and ($options{mode}>0)){ $options{mode}--; reset_all(0); }
    elsif(($key=~/2/i) and ($options{mode}<3)){ $options{mode}++; reset_all(0); }
    elsif($key=~/c/i) { 
	$options{color}++;
	if ($options{color} >=3) { $options{color}=0; } }
    elsif($key=~/t/i) { 
	if ($options{time} == 0) { $options{time}=1; }
	else { $options{time}=0; } }

    # Seeking
    elsif($key=~/x/i) { reset_all(0);
    	if (($options{time} > 0) and ($tsize <= ($options{tres}-5))) { $t += 5*($options{tmax}-$options{tmin})/($options{tres}-1); $tsize=$tsize+5; }
	elsif ($tsize < $options{tres}) { $t += ($options{tmax}-$options{tmin})/($options{tres}-1); $tsize++; } } 
    elsif($key=~/y/i) { reset_all(0);
    	if (($options{time} > 0) and ($tsize >= 5)) { $t -= 5*($options{tmax}-$options{tmin})/($options{tres}-1); $tsize=$tsize-5; }
	elsif ($tsize > 0) { $t -= ($options{tmax}-$options{tmin})/($options{tres}-1); $tsize--; } } 

    # Resolution
    elsif($key=~/u/i){ $options{xres}=int($options{xres}*1.1+0.9); $options{zres}=int($options{zres}*1.1+0.9); reset_all(1); }
    elsif($key=~/z/i){ $options{xres}=int($options{xres}*0.91+0.2); $options{zres}=int($options{zres}*0.91+0.2); reset_all(1); }
    elsif($key=~/j/i){ $options{tres}=int($options{tres}*1.1+0.9); reset_all(1); }
    elsif($key=~/h/i){ $options{tres}=int($options{tres}*0.91+0.2); reset_all(1); }

    # Rotation
    elsif($key=~/w/i){ glRotatef(3, 1, 0, 0); }
    elsif($key=~/s/i){ glRotatef(-3, 1, 0, 0); }
    elsif($key=~/a/i){ glRotatef(3, 0, 1, 0); }
    elsif($key=~/d/i){ glRotatef(-3, 0, 1, 0); }

    # Translation
    elsif($key=~/\ü/i){ glTranslatef(0, 0.5, 0); }
    elsif($key=~/\ä/i){ glTranslatef(0, -0.5, 0); }
    elsif($key=~/\ö/i){ glTranslatef(-0.5, 0, 0); }
    elsif($key=~/\$/i){ glTranslatef(0.5, 0, 0); }

    # Scaling
    elsif($key=~/r/i){ glScalef(1.1,1.1,1.1); }
    elsif($key=~/e/i){ glScalef(0.91,0.91,0.91); }
}



# SCREENSHOT HEADER
sub getheader{
    my $header;
    my ($width,$height) = @_;

    $header =  pack("s",hex("4d42"));	        #signature
    $header .= pack("i",$width*$height*3+hex(36));#size (inc header)
    $header .= pack("s",0);			#reserved
    $header .= pack("s",0);			#reserved
    $header .= pack("i",hex(36));		#offset
    $header .= pack("i",40);		        #size of BITMAPINFOHEADER structure, must be 40
    $header .= pack("i",$width);		#width
    $header .= pack("i",$height);		#hight
    $header .= pack("s",1);			#number of planes in the image, must be 1
    $header .= pack("s",24);	                #bites per pixel
    $header .= pack("i",0);			#compression type (0=none, 1=RLE-8, 2=RLE-4)
    $header .= pack("i",$width*$height*3);	#size of image data
    $header .= pack("i",0);			#horizontal resolution in pixels per meter (unreliable)
    $header .= pack("i",0);			#vertical resolution in pixels per meter (unreliable)
    $header .= pack("i",0);			#number of colors in image, or zero
    $header .= pack("i",0);			#number of important colors, or zero

    return $header;
}


# SCREENSHOT
sub screenshot {
    my $num = 1;
    my $i;

    $num++ while(-f "$options{image_name}_$num.bmp");

    if(open(IMAGE,">$options{image_name}_$num.bmp")){
        print IMAGE getheader($options{width},$options{height});
        my @pixels=glReadPixels_p(0, 0,$options{width},$options{height},GL_RGB,GL_UNSIGNED_INT);
        for $i (0..(($#pixels-1)/3)){
            my ($red, $green, $blue) = ($pixels[$i*3],$pixels[$i*3+1],$pixels[$i*3+2]);
            print IMAGE pack("B B B",$blue,$green,$red); }
        close(IMAGE);
    }
}



# SUB IDLE (2D)
sub idle_2d {
    my (@tripar,$x,$y,$j,$eval_block);
    $x=$options{xmin};
    $j=0;
    
    $eval_block= '
    while ($j<=(2*$options{xres}-2)) {
    ';
    $eval_block.= "\$y = $yreal[$n];";
    $eval_block.= '
        $tripar[$j]=$x;
        $tripar[$j+1]=$y;
        $x += ($options{xmax}-$options{xmin})/($options{xres}-1); 
	$j=$j+2;
    }
    return 1;
    ';
    
    while ($j<=(2*$options{xres}-2)){
        last if eval($eval_block);
        $tripar[$j]=$x;
        $tripar[$j+1]=0;
        $x += ($options{xmax}-$options{xmin})/($options{xres}-1);
        $j=$j+2;
    }
    
    $j = 0;
    $zsize = $options{zres} + 1;
	
    unshift(@tri2d,@tripar); 
    splice(@tri2d,(2*$options{xres})); 
}    


# SUB IDLE (3D)
sub idle_3d {
    my (@tripar,$x,$y,$j,$eval_block);
    $x=$options{xmin};
    $j=0;
    
    $eval_block='
    while ($j<=(3*$options{xres}-3)) {
    ';
    $eval_block.= "\$y = $yreal[$n];";
    $eval_block.= '	
        $tripar[$j]=$x;
        $tripar[$j+1]=$y;
        $tripar[$j+2]=$z;
        $x += ($options{xmax}-$options{xmin})/($options{xres}-1); 
        $j=$j+3;
    }
    return 1;
    ';
    
    while ($j<=(3*$options{xres}-3)){
        last if eval($eval_block);
        $tripar[$j]=$x;
        $tripar[$j+1]=0;
        $tripar[$j+2]=$z;
        $x += ($options{xmax}-$options{xmin})/($options{xres}-1);
        $j=$j+3;
    }

    $j = 0;
    $z += ($options{zmax}-$options{zmin})/($options{zres}-1); 
    $zsize++; 		# IMPORTANT !!!!

    unshift(@tri3d,@tripar); 
    splice(@tri3d,(9*$options{xres}*$options{zres})); 
}    



# SUB DISPLAY (2D)
sub display_2d {
    my $i;

    glBegin(GL_LINE_STRIP); {
        if ($options{color} > 0) {scolor($tri2d[1]);}
        glVertex2f($tri2d[0],$tri2d[1]);
        for ($i=2; $i<=(2*$options{xres}-2); $i=$i+2) { 
    	    if ($options{color} > 0) {scolor($tri2d[$i+1]); }
    	    glVertex2f($tri2d[$i],$tri2d[$i+1]);}
    } glEnd();
}


# SUB DISPLAY (3D WIRE)
sub display_3dwire {
    my ($h,$i);
    
    for $h (0..($zsize-2)) {
        glBegin(GL_LINE_STRIP); {
    	    # jeweils um einen Punkt springen
    	    for ($i=0; $i<=(3*$options{xres}-4); $i=$i+3) {
		my $basec = $i+1+3*$h*$options{xres};
		# left bottom
		if ($options{color} > 0) {scolor($tri3d[$basec]); }
		glVertex3f($tri3d[$basec-1],$tri3d[$basec],$tri3d[$basec+1]);
		# left top
		if ($options{color} > 0) {scolor($tri3d[$basec+3*$options{xres}]); }
		glVertex3f($tri3d[$basec-1+3*$options{xres}],$tri3d[$basec+3*$options{xres}],$tri3d[$basec+1+3*$options{xres}]);
		# rigth top
		if ($options{color} > 0) {scolor($tri3d[$basec+3+3*$options{xres}]); }
		glVertex3f($tri3d[$basec+2+3*$options{xres}],$tri3d[$basec+3+3*$options{xres}],$tri3d[$basec+4+3*$options{xres}]);
		# left bottom
		if ($options{color} > 0) {scolor($tri3d[$basec]); }
		glVertex3f($tri3d[$basec-1],$tri3d[$basec],$tri3d[$basec+1]); }
		if ($options{color} > 0) {scolor($tri3d[3*$options{xres}-2+3*$h*$options{xres}]); }
		glVertex3f($tri3d[3*$options{xres}-3+3*$h*$options{xres}],$tri3d[3*$options{xres}-2+3*$h*$options{xres}],$tri3d[3*$options{xres}-1+3*$h*$options{xres}]); 
		if ($options{color} > 0) {scolor($tri3d[3*$options{xres}-2+3*$h*$options{xres}+3*$options{xres}]); }
		glVertex3f($tri3d[3*$options{xres}-3+3*$h*$options{xres}+3*$options{xres}],$tri3d[3*$options{xres}-2+3*$h*$options{xres}+3*$options{xres}],$tri3d[3*$options{xres}-1+3*$h*$options{xres}+3*$options{xres}]); 
	} glEnd();
    }
}


# SUB DISPLAY (3D SOLID)
sub display_3d {
    my ($h,$i);

    glBegin(GL_TRIANGLES);
    {
	for $h (0..($zsize-2)) {
	    for ($i=0; $i<=(3*$options{xres}-4); $i=$i+3) {
		my $basec = $i+1+3*$h*$options{xres};
		# left bottom
		if ($options{color} > 0) {scolor($tri3d[$basec]); }
		glVertex3f($tri3d[$basec-1],$tri3d[$basec],$tri3d[$basec+1]);
		# left top
		if ($options{color} > 0) {scolor($tri3d[$basec+3*$options{xres}]); }
		glVertex3f($tri3d[$basec+3*$options{xres}-1],$tri3d[$basec+3*$options{xres}],$tri3d[$basec+3*$options{xres}+1]); 
		# right bottom
		if ($options{color} > 0) {scolor($tri3d[$basec+3]); }
		glVertex3f($tri3d[$basec+2],$tri3d[$basec+3],$tri3d[$basec+4]); 
		# left top
		if ($options{color} > 0) {scolor($tri3d[$basec+3*$options{xres}]); }
		glVertex3f($tri3d[$basec+3*$options{xres}-1],$tri3d[$basec+3*$options{xres}],$tri3d[$basec+3*$options{xres}+1]); 
		# right top
		if ($options{color} > 0) {scolor($tri3d[$basec+3*$options{xres}+3]); }
		glVertex3f($tri3d[$basec+3*$options{xres}+2],$tri3d[$basec+3*$options{xres}+3],$tri3d[$basec+3*$options{xres}+4]); 
		# right bottom
		if ($options{color} > 0) {scolor($tri3d[$basec+3]); }
		glVertex3f($tri3d[$basec+2],$tri3d[$basec+3],$tri3d[$basec+4]); 
	    }
	}
    } glEnd();
}
