#!/usr/bin/env perl
use strict;
$^W=1;

use blib;
use ToyGL ':all';
use Quaternion;

use Math::Trig;
use SDL;
use SDL::Surface;
use SDL::Event;
use SDL::Cursor;



# State of the mouse.

my ($mousex,$mousey)=(0,0);
my ($bx,$by)=(0,0);
my $movemode=0;
my $leftbutdown=0;
my $rightbutdown=0;
my $midbutdown=0;

# SDL doesn't define mouse-wheel event symbols, so I guess I get to do
# it myself.  In fact, perl's SDL and SDL::Event don't define the button
# symbols at all. Good to see the API is as consistent and polished as
# its documentation.

use constant {
	SDL_BUTTON_LEFT   => 1,
	SDL_BUTTON_MIDDLE => 2,
	SDL_BUTTON_RIGHT  => 3,
	WHEELUP	          => 4,
	WHEELDOWN         => 5,
};

# Some colours.
my @black =   (0,0,0,1);
my @grey  =   (0.5,0.5,0.5,1);
my @white =   (1,1,1,1);
my @red =     (1,0,0,1);
my @yellow =  (1,1,0,1);
my @green =   (0,1,0,1);
my @cyan =    (0,1,1,1);
my @blue =    (0,0,1,1);
my @magenta = (1,0,1,1);

# Various screen-state variables.

my $nearclip = 0.1;
my ($geomx,$geomy,$geomz) = (0,0,-5);
my $frametimems = 20;
my ($screenx,$screeny) = (512,512);
my $scx = $screenx/2;
my $scy=$screeny/2;
my $sphererad = $screeny*0.5;
my $orquat = Quaternion::rotation(0,0,0,1);

my $teapotlist=undef;
my $needredraw=0;	# Set if the screen ever changes.

my ($xrot,$yrot,$zrot)=(0,0,0);

my @spiralpoints = ();
{
	my $npoints  = 100;
	my $nturns = 4;
	my $dtheta = $nturns * 2.0*pi / $npoints;
	for my $i (1..$npoints) {
		my $theta = $i * $dtheta;
		my $r = 0.25*$theta / (2.0 * pi);
		my $x = $r * cos($theta);
		my $y = $r * sin($theta);
		my $z = $r;

		push @spiralpoints,[ $x, $y, $z ];
	}
}

my @ballpoints = ();
my @ballsticks = ();

my $nballpoints = 10;
my $nballsticks=10;
sub genballpoints {
	@ballpoints = map {
		[  map { 2*rand()-1 } 1..3 ]
	} 1..$nballpoints;
}

sub genballsticks {
	@ballsticks = ();
	for (1..$nballsticks) {
		my $i1 = rand @ballpoints;
		my $i2 = rand @ballpoints;
		push @ballsticks, @ballpoints[$i1,$i2];
	}
}

genballpoints;
genballsticks;
#print "points\n";
#map { print join(":",@$_),"\n"; } @ballpoints;
#print "lines\n";
#map { print join(":",@$_),"\n"; } @ballsticks;

my $geometry = 0;

my @geomcolour= ( (map { rand } 1..3), 1);

sub randomize_colour {
	@geomcolour = ( (map { rand } 1..3), 1);
}

my @drawgeometry = (
	sub {
		# Wire teapot
		glFrontFace(GL_CW);
		glutSolidTeapot(1.0);
		glFrontFace(GL_CCW);
	},
	sub { glutSolidTetrahedron(); },
	sub { glutSolidCube(1.0); },
	sub { glutSolidOctahedron(); },
	sub { glutSolidDodecahedron(); },
	sub { glutSolidIcosahedron(); },
	# Grey cube with funky neon outline
	sub {
		glColor(@grey);
		glutSolidCube(1.0);
		glDisable(GL_LIGHTING);
		glColor(@red);
		glutWireCube(1.0);
		glEnable(GL_LIGHTING);
	},
	sub { glutSolidTorus(0.4,1.0,16,32); },
	sub { glutWireSphere(1.0,16,32); },
	sub {
		glDisable(GL_LIGHTING);
		glBegin(GL_LINE_STRIP);
		map {
			glVertex(@$_);
		} @spiralpoints;
		glEnd;
		glEnable(GL_LIGHTING);
	},
	sub {
		# Draw lots of red balls.
		glColor(@red);
		map {
			glPushMatrix;
				glTranslate(@$_);
				glutSolidSphere(0.1,8,8);
			glPopMatrix;
		} @ballpoints;

		# Draw gray sticks joining them.
		glDisable(GL_LIGHTING);
		glColor(@grey);
		glLineWidth(10.0);
		glBegin(GL_LINES);
		map {
			glVertex(@$_);
		} @ballsticks;
		glEnd;
		glEnable(GL_LIGHTING);
		glLineWidth(3.0);
	}
);

sub updatescreen {
	&SDL::GLSwapBuffers;
}

sub init_gl {
	my @ambientlight = ( 0,0,0,1);
	my @diffuselight = ( 1,1,1,1 );

	glViewport(0,0,$screenx,$screeny);

	glShadeModel(GL_SMOOTH);

	glClearColor(0.0,0.5,0.7,0.0);
	glClearDepth(1.0);

	glLight(GL_LIGHT1,GL_AMBIENT,@ambientlight);
	glLight(GL_LIGHT1,GL_DIFFUSE,@diffuselight);
	glEnable(GL_LIGHT1);
	glEnable(GL_LIGHTING);

	glDepthFunc(GL_LESS);
	glDepthMask(GL_TRUE);
	glEnable(GL_DEPTH_TEST);

	glCullFace(GL_BACK);
	glEnable(GL_CULL_FACE);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

	glEnable(GL_POLYGON_OFFSET_FILL);

	glDisable(GL_TEXTURE_2D);

	glColorMaterial(GL_FRONT,GL_AMBIENT_AND_DIFFUSE);
	glEnable(GL_COLOR_MATERIAL);

	glPolygonOffset(1.0,1.0);
	glEnable(GL_POLYGON_OFFSET_FILL);

	glHint(GL_LINE_SMOOTH_HINT,GL_NICEST);
	glEnable(GL_LINE_SMOOTH);

	glLineWidth(3.0);



}

sub draw_gl {

	# Set for perspective projection 

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0,$screenx/$screeny,$nearclip,1024.0 );
	glMatrixMode(GL_MODELVIEW);

	glDepthMask(GL_TRUE);
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

	# Position and orient geometry 
	glLoadIdentity();
	glTranslate($geomx,$geomy,$geomz);
	my @m = $orquat->glmatrix;
	glMultMatrix(@m);

	glColor(@geomcolour);
	&{$drawgeometry[$geometry]}; 
	
#	my $rotquat = Quaternion::rotation(0.01,1,1,0);
#	$orquat = Quaternion::multiply($rotquat,$orquat);
#	# If you run for lots and lots of frames, floating-point error
#	# eventually skews the quaternion somewhat, so it's no longer
#	# a proper rotation quat. Regular normalization helps avoid this.
#	$orquat = $orquat->normalize;
	

	$needredraw=0;
}

# Clear up and quit. 
sub quit_all {

	#SDL_ShowCursor(SDL_ENABLE);
	exit(0);
}

sub mousewheelup {
	$geometry = ($geometry+1)%(scalar @drawgeometry);
	randomize_colour;
	$needredraw=1;
}
sub mousewheeldown {
	$geometry = ($geometry-1)%(scalar @drawgeometry);
	randomize_colour;
	$needredraw=1;
}

sub mousezoommotion {
	my $dz = shift;
	my $zoomscale=0.02;
	$geomz -= $zoomscale*$dz;
	$needredraw=1;
}

sub mouserotatemotion {
	my ($x0,$y0,$x1,$y1) = @_;


	my $s = $sphererad;
	my $my = $x1-$x0;
	my $mx = $y1-$y0;
	my $m=sqrt($mx*$mx+$my*$my);

	my $theta;

	if (($m>0) && ($m<$s)) {
		$theta = $m/$s;

		$mx /= $m;
		$my /= $m;

		my $rotquat = Quaternion::rotation($theta,$mx,$my,0.0);
		$orquat = Quaternion::multiply($rotquat,$orquat);
	}

	$needredraw=1;
	#viewerCalcForward(myview);
}

sub handle_tag_event {
	my $event = shift;
	print "Tag at $mousex , $mousey\n";
}

sub handle_regen {
	genballpoints;
	genballsticks;
	$needredraw=1;
}

sub handle_keypress {
	my $event = shift;
	my $unicode = $event->key_unicode;

	if ($unicode != 0) {
		my $key = chr($unicode);
		if (" " eq $key) {
			handle_tag_event($event);
		} elsif ("q" eq $key) {
			quit_all;
		} elsif ("r" eq $key) {
			handle_regen;
		} elsif ("a" eq $key) {
			mousewheelup();
		} elsif ("z" eq $key) {
			mousewheeldown();
		}
	} else {
		# Key has no ASCII code (like CTRL etc)
	}
}


############################################################
# Start of main code

SDL::Init(SDL::INIT_VIDEO);
SDL::EnableUnicode(1);
my $surface 
 = SDL::SetVideoMode($screenx,$screeny,16,SDL::OPENGL|SDL::RESIZABLE);


{
my $event = new SDL::Event;
my $frameno=0;



# Set GL options: >=15-bit colour, 16-bit Z, double-buffered. 

#	SDL_GL_SetAttribute(SDL_GL_RED_SIZE,5);
#	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,5);
#	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,5);
#	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,16);
#	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);


# Initialize GL 

init_gl();

# Main loop 

my $event = new SDL::Event;
$needredraw=1;

while(1) {

	# Render frame to screen 
	if ($needredraw) {
		draw_gl();
	}
	SDL::GLSwapBuffers();

	my $gotevent=1;

	$event->wait; # Block until something happens.

	do {
		my $type = $event->type;
		if (SDL_QUIT == $type) {
			quit_all;
		} elsif (SDL_KEYDOWN == $type) {
			handle_keypress($event);
		} elsif (SDL_MOUSEBUTTONDOWN == $type) {
			my $bevent = $event->button;
			if (SDL_BUTTON_LEFT == $bevent) {
				$leftbutdown = 1;
				$bx = $event->button_x;
				$by = $event->button_y;
			} elsif (SDL_BUTTON_RIGHT == $bevent) {
				$rightbutdown=1;
				$bx = $event->button_x;
				$by = $event->button_y;
			} elsif (SDL_BUTTON_MIDDLE == $bevent) {
				$midbutdown=1;
				$bx = $event->button_x;
				$by = $event->button_y;
			} elsif (WHEELDOWN == $bevent) {
				mousewheelup();
			} elsif (WHEELUP == $bevent) {
				mousewheeldown();
			}
		} elsif (SDL_MOUSEBUTTONUP == $type) {
			my $bevent = $event->button;
			if (SDL_BUTTON_LEFT == $bevent) {
				$leftbutdown = 0;
			} elsif (SDL_BUTTON_RIGHT == $bevent) {
				$rightbutdown=0;
			} elsif (SDL_BUTTON_MIDDLE == $bevent) {
				$midbutdown=0;
			}
		} elsif (SDL_MOUSEMOTION == $type) {
			$mousex = $event->motion_x;
			$mousey = $event->motion_y;
			if ((0!=$event->motion_xrel) ||
				(0!=$event->motion_yrel)) {

				if ($leftbutdown) {
					mouserotatemotion($bx,$by,
						$mousex,$mousey);
						SDL::WarpMouse($bx,$by);
						$mousex=$bx;$mousey=$by;
				} elsif ($rightbutdown) {
					mousezoommotion(
						$event->motion_y-$by);
						SDL::WarpMouse($bx,$by);
						$mousex=$bx;$mousey=$by;
				}
			}
		}

		if ($leftbutdown && (!$movemode) ) {
			$movemode=1;
			SDL::ShowCursor(0);
		} elsif ( (!$leftbutdown) && ($movemode) ) {
			$movemode=0;
			SDL::ShowCursor(1);
		}

		if ($leftbutdown) { $movemode=1; } else { $movemode=0; }
	} while ($event->poll)

} # while(1)
return 0;
}	
