#!/usr/bin/perl
#
# Name:		GLgraph
#
# Version:	0.1.4
#
# License:	GPL
#
# 2002/04/12	Jonas Jermann <jjermann@gmx.ch>
#
# pfy rulez!	(He told me to write it down...)
#
################################################################################
#
# M O D U L E S

use OpenGL ":old",":glutfunctions",":gluconstants",":functions";
use Time::HiRes qw (gettimeofday tv_interval usleep);
use strict; no strict "subs";
use Math::Trig;

################################################################################
#
# V A R I A B L E S

our ($PI,$tsize,$zsize,$time,$n,$z,$t,@tri2d,@tri3d, @yreal,@options,$cfact,$fps,$xmin,$xmax,$ymin,$ymax,$zmin,$zmax,$tmin,$tmax,$xres,$zres,$tres, $width,$height);


@options = (2,0,0,1);

# 0(0): 2d
# 0(1): 3d wire
# 0(2): 3d solid
# 1(0): stable
# 1(1): time-function forward
# 1(2): time-function backward
# 2(0): orthographic viewing
# 2(1): perspective viewing
# 2(2): stereo viewing
# 2(3): 2nd way of stereo viewing
# 3(0): no colours
# 3(1): color-mode 1
# 3(2): color-mode 2 (pfy)

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

# Axis and misc
$xres = 40;
$zres = 50;
$tres = 50;
$fps = 30;
$cfact = 1.5;

# Init
$z = $zmin;
$t = $tmin;
$n = 0;
$time = [gettimeofday];
$zsize = 0;
$tsize = 0;
$PI = 3.415926535898;


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

init();
glutMainLoop;

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

# INIT
sub init{
    # glut init
    glutInit(@ARGV);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(1024, 768);
    glutInitWindowPosition(0, 0);
    glutCreateWindow("graph window");
    glutFullScreen();

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

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

    glOrtho($xmin,$xmax,$ymin,$ymax,$zmin,$zmax);
    glRotatef(-50, 5, -0.5, 0.0);
    glScalef(0.8,0.8,0.8); 

    reset();
}    



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



sub resize{ ($width,$height)=@_; }

sub reset {
    # GL init
    glLoadIdentity();
    # Window properties
    glFlush();
    # Calculation
    idle();
}



# IDLE
sub idle {
    $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] = 'x*x*x';
    $yreal[4] = 'sin(x*x+z*z+t*t)';

    $yreal[$n] =~ s/(?<!\w)([xzt])(?!\w)/\$$1$2/g;
    $yreal[$n] =~ s/PI/\$PI/g;
    
    if ($zsize < $zres) {
        if ($options[0] == 0) {
            idle_2d();
            display();
        }
        if ($options[0] >= 1) {
    	    idle_3d();
    	    if ($zsize == $zres) {display();}
    	}
    }

    elsif (($options[1] == 1) and ($tsize < $tres)) { $zsize=0;	$z=$zmin; $t += ($tmax-$tmin)/($tres-1); $tsize++; }
    elsif (($options[1] == 2) and ($tsize > 0)) { $zsize=0; $z=$zmin; $t -= ($tmax-$tmin)/($tres-1); $tsize--; }
    elsif ($options[1] == 1) { $options[1]=2; }
    elsif ($options[1] == 2) { $options[1]=1; }
}
    


# MAIN DISPLAY
sub display {
    if (tv_interval($time)<(1/$fps)) { usleep (1/120); return; }
    $time = [gettimeofday];

    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glColor3f(0.8,0.8,0.8);
    
    if    ($options[0] == 0) { display_2d(); }
    elsif ($options[0] == 1) { display_3dwire(); }
    elsif ($options[0] == 2) { display_3d(); }

    glFlush();
    glutSwapBuffers();    
}



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

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

    # Mode change
    elsif(($key=~/1/i) and ($options[0]>0)){ $options[0]--; $zsize=0; $z=$zmin; }
    elsif(($key=~/2/i) and ($options[0]<2)){ $options[0]++; $zsize=0; $z=$zmin; }
    elsif($key=~/c/i) { 
	if ($options[3] < 3) { $options[3]++; }
	if ($options[3] >=3) { $options[3]=0; } }
    elsif($key=~/t/i) { 
	if ($options[1] == 0) { $options[1]=1; }
	else { $options[1]=0; } }

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

    # Resolution (! BUGGY !)
    elsif($key=~/u/i){ $t=$tmin; $z=$zmin; $xres=int($xres*1.1+0.9); $zres=int($zres*1.1+0.9); }
    elsif($key=~/z/i){ $t=$tmin; $z=$zmin; $xres=int($xres*0.91+0.2); $zres=int($zres*0.91+0.2); }
    elsif($key=~/j/i){ $t=$tmin; $z=$zmin; $tsize=0; $zsize=0; $tres=int($tres*1.1+0.9); }
    elsif($key=~/h/i){ $t=$tmin; $z=$zmin; $tsize=0; $zsize=0; $tres=int($tres*0.91+0.2); }

    # 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); }

    # Repaint the window
    display(); 
}



# 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 "image$num.bmp");

    if(open(IMAGE,">image$num.bmp")){
        print IMAGE getheader($width,$height);
        my @pixels=glReadPixels_p(0, 0,$width,$height, GL_RGB,GL_UNSIGNED_INT);
        #{print (IMAGE pack("B*", $i )); }for $i (@pixels);
        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); }
        #for $pixel (@pixels){ print IMAGE pack ("B*",$pixel);}
        close(IMAGE);
    }
}



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


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

    $j = 0;
    $z += ($zmax-$zmin)/($zres-1); $zsize++; 

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



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

    glBegin(GL_LINE_STRIP); {
        if ($options[3] > 0) {scolor($tri2d[1]);}
        glVertex2f($tri2d[0],$tri2d[1]);
        for ($i=2; $i<=(2*$xres-2); $i=$i+2) { 
    	    if ($options[3] > 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*$xres-4); $i=$i+3) {
		my $basec = $i+1+3*$h*$xres;
		# left bottom
		if ($options[3] > 0) {scolor($tri3d[$basec]); }
		glVertex3f($tri3d[$basec-1],$tri3d[$basec],$tri3d[$basec+1]);
		# left top
		if ($options[3] > 0) {scolor($tri3d[$basec+3*$xres]); }
		glVertex3f($tri3d[$basec-1+3*$xres],$tri3d[$basec+3*$xres],$tri3d[$basec+1+3*$xres]);
		# rigth top
		if ($options[3] > 0) {scolor($tri3d[$basec+3+3*$xres]); }
		glVertex3f($tri3d[$basec+2+3*$xres],$tri3d[$basec+3+3*$xres],$tri3d[$basec+4+3*$xres]);
		# left bottom
		if ($options[3] > 0) {scolor($tri3d[$basec]); }
		glVertex3f($tri3d[$basec-1],$tri3d[$basec],$tri3d[$basec+1]); }
		if ($options[3] > 0) {scolor($tri3d[3*$xres-2+3*$h*$xres]); }
		glVertex3f($tri3d[3*$xres-3+3*$h*$xres],$tri3d[3*$xres-2+3*$h*$xres],$tri3d[3*$xres-1+3*$h*$xres]); 
		if ($options[3] > 0) {scolor($tri3d[3*$xres-2+3*$h*$xres+3*$xres]); }
		glVertex3f($tri3d[3*$xres-3+3*$h*$xres+3*$xres],$tri3d[3*$xres-2+3*$h*$xres+3*$xres],$tri3d[3*$xres-1+3*$h*$xres+3*$xres]); 
	} glEnd();
    }
}


# SUB DISPLAY (3D SOLID)
sub display_3d {
    my ($h,$i);
    $time = [gettimeofday];

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