#!/usr/bin/perl

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

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


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

# points/axis and fps
$xres = 200;
$zres = 300;
$tres = 200;
$fps = 120;

# variables init
$PI = 3.415926535898;
$z = $zmin;
$t = $tmin;
$time = [gettimeofday];
$zsize = 0;
$tsize = 0;
	# 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
@options = (2,0,0);


# main program ;-)
init();
glutMainLoop;


sub init{
    # glut init
    glutInit(@ARGV);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_ACCUM);
    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(-40, 5, -0.5, 0.0);
    glScalef(0.85,0.85,0.85); 

    reset();
}    





sub idle {
    my (@tripar,$dim,$x,$y,$j);
    
    if ($options[0] == 0) {$dim=2;} 
    else {$dim=3;}

    if ($zsize < $zres) {
	$x=$xmin;
	for ($j=0; $j<=($dim*$xres-$dim); $j=$j+$dim) {

    	    eval { $y=cos($t*sqrt($z*$z+$x*$x)-2*atan($x/$z)); };

    	    $tripar[$j]=$x;
    	    $tripar[$j+1]=$y;
    	    if ($dim == 3) {$tripar[$j+2]=$z;}
    	    $x += ($xmax-$xmin)/($xres-1); 
	}

	$j = 0;
	if ($dim == 3) {
	    $z += ($zmax-$zmin)/($zres-1);
	    $zsize++; }
	elsif ($dim == 2) {
	    $zsize = $zres + 1;}
	
#	if (tv_interval($time)<(1/$fps)) { 
#	    usleep (1/120); 
#	    return; }
#	$time = [gettimeofday];
	
	if ($dim == 3) { 
	    unshift(@tri3d,@tripar);
	    splice(@tri3d,(9*$xres*$zres)); 
	    if ($zsize == $zres) {display();} }
	else {
	    unshift(@tri2d,@tripar);
	    splice(@tri2d,(2*$xres)); 
	    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;
    }

}
    

sub display {
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glColor3f(1,1,1);
    
    my ($h,$i);

    # display2d()
    if ($options[0] == 0) {
	glColor3f(0.8,0.8,0.8);
	glBegin(GL_LINE_STRIP); {
	    glVertex2f($tri2d[0],$tri2d[1]);
	    for ($i=2; $i<=(2*$xres-2); $i=$i+2) { glVertex2f($tri2d[$i],$tri2d[$i+1]); }
	} glEnd();
    }
    
    # display3d_wire()
    elsif ($options[0] == 1) {
	glColor3f(0.8,0.8,0.8);
	for $h (0..($zsize-2)) {
	    glBegin(GL_LINE_STRIP); {
	    	# jeweils um einen Punkt springen
	    	for ($i=0; $i<=(3*$xres-4); $i=$i+3) {
		    # left bottom
		    glVertex3f($tri3d[$i+3*$h*$xres],$tri3d[$i+1+3*$h*$xres],$tri3d[$i+2+3*$h*$xres]);
		    # left top
		    glVertex3f($tri3d[$i+3*$xres+3*$h*$xres],$tri3d[$i+3*$xres+1+3*$h*$xres],$tri3d[$i+3*$xres+2+3*$h*$xres]);
		    # rigth top
		    glVertex3f($tri3d[$i+3*$xres+3+3*$h*$xres],$tri3d[$i+3*$xres+4+3*$h*$xres],$tri3d[$i+3*$xres+5+3*$h*$xres]);
		    # left bottom
		    glVertex3f($tri3d[$i+3*$h*$xres],$tri3d[$i+1+3*$h*$xres],$tri3d[$i+2+3*$h*$xres]); }
	    } glEnd();
	}
    }
    
    # display3d_solid()
    # FALSCH
    elsif ($options[0] == 2) {
	for $h (0..($zsize-2)) {
	    glBegin(GL_TRIANGLE_STRIP);
	    {
		my $basec = 3*$xres+1+3*$h*$xres;
#		glColor3f(abs($tri3d[$basec]/($ymax-$ymin))*2,0,0);
		scolor($tri3d[$basec],2);
		# left top
		glVertex3f($tri3d[$basec-1],$tri3d[$basec],$tri3d[$basec+1]);
	    	for ($i=3; $i<=(3*$xres-4); $i=$i+3) {
		    my $basec = $i+1+3*$h*$xres;
#		    glColor3f(abs($tri3d[$basec]/($ymax-$ymin))*2,0,0);
		    scolor($tri3d[$basec],2);
		    # left bottom
		    glVertex3f($tri3d[$basec-1],$tri3d[$basec],$tri3d[$basec+1]);
		    # right top
		    glVertex3f($tri3d[$basec+3*$xres-1],$tri3d[$basec+3*$xres],$tri3d[$basec+3*$xres+1]); }
		
	    } glEnd();
	}
    }
    
    glFlush();
    glutSwapBuffers();    
}


sub scolor {
    my $scolor = $_[0];
    my $cfact = $_[1];
    glColor3f(0.8*sin($scolor*$cfact),0.8*sin($scolor*$cfact+$PI/2),0.8*sin($scolor*$cfact+$PI));
}

sub keyboard {
    my ($key) = @_;
    $key = chr($key);
    if($key=~/w/i){
    # rotate up
	glRotatef(3.0, 0.5, 0, 0.0); }
    elsif($key=~/s/i){
    # rotate down
        glRotatef(-3.0, 0.5, 0, 0.0); }
    elsif($key=~/a/i){
    # rotate left
        glRotatef(3.0, 0.0, 0.5, 0); }
    elsif($key=~/d/i){
    # rotate right
        glRotatef(-3.0, 0.0, 0.5, 0); }
    elsif($key=~/q/i){
        exit(); }
    elsif($key=~/m/i){
        screenshot(); }
    elsif($key=~/r/i){
        glScalef(1.1,1.1,1.1); }
    elsif($key=~/f/i){
        glScalef(0.9,0.9,0.9); }

    elsif(($key=~/t/i) and ($tsize < $tres)) {
	$zsize=0;
	$z=$zmin;
	$t += ($tmax-$tmin)/($tres-1);
	$tsize++;} 
    elsif(($key=~/g/i) and ($tsize > 0)) {
	$zsize=0;
	$z=$zmin;
	$t -= ($tmax-$tmin)/($tres-1);
	$tsize--;} 

    # repaint the window
    display(); 
}


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


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

    $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;
}


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 reset {
    # gl init
    glLoadIdentity();

    # light
#    my @mat_diffuse 	= (1.0, 1.0, 1.0, 1.0);
#    my $mat_shininess 	= 50.0;
#    my @light_position	= ($xmax,$ymax,$zmax,1);
#    my @light_ambient 	= (1.0, 1.0, 1.0, 1.0);
#    glShadeModel (GL_SMOOTH);
#    OpenGL::glMaterialfv_p(GL_FRONT, GL_DIFFUSE,  @mat_diffuse);
#    OpenGL::glMaterialfv_p(GL_FRONT, GL_SHININESS, $mat_shininess);
#    OpenGL::glLightfv_p(GL_LIGHT0, GL_POSITION, @light_position);
#    OpenGL::glLightfv_p(GL_LIGHT0, GL_AMBIENT, @light_ambient);
#    glEnable(GL_LIGHTING);
#    glEnable(GL_LIGHT0);
#    glEnable(GL_DEPTH_TEST);
           
    # window properties
    glFlush();
    
    # calculation
    idle();
}


#- eval error
#- perldoc -f eval
# color(rot,grün,blau)