# NeHe Tutorial Lesson: 42 - Multiple Viewports
#
# Ported to PyOpenGL 2.0 by Brian Leair 18 Jan 2004
#
# This code was created by Jeff Molofee 2000
#
# The port was based on the PyOpenGL tutorials and from 
# PyOpenGLContext (tests/glprint.py)
#
# If you've found this code useful, feel free to let me know 
# at (Brian Leair telcom_sage@yahoo.com).
#
# See original source and C based tutorial at http://nehe.gamedev.net
#
# Note:
# -----
# This code is not an ideal example of Pythonic coding or use of OO techniques.  
# It is a simple and direct exposition of how to use the Open GL API in 
# Python via the PyOpenGL package. It also uses GLUT, a high quality 
# platform independent library. Due to using these APIs, this code is 
# more like a C program using procedural based programming.
#
# To run this example you will need:
# Python 	- www.python.org (v 2.3 as of 1/2004)
# PyOpenGL 	- pyopengl.sourceforge.net (v 2.0.1.07 as of 1/2004)
# Numeric Python	- (v.22 of "numpy" as of 1/2004) numpy.sourceforge.net
#
# Make sure to get versions of Numeric, and PyOpenGL to match your
# version of python.
#
#
# /***************************************************************************************************************
# *                                                      *                                                       *
# *  Lesson 42: Multiple Viewports                       *  Created:  05/17/2003                                 *
# *                                                      *                                                       *
# *  This Program Was Written By Jeff Molofee (NeHe)     *  Runs Much Faster (Many Useless Loops Removed)        *
# *  From http://nehe.gamedev.net.                       *                                                       *
# *                                                      *  Maze Code Is Still Very Unoptimized.  Speed Can Be   *
# *  I Wanted To Create A Maze, And Was Able To Find     *  Increased Considerably By Keeping Track Of Cells     *
# *  Example Code, But Most Of It Was Uncommented And    *  That Have Been Visited Rather Than Randomly          *
# *  Difficult To Figure Out.                            *  Searching For Cells That Still Need To Be Visited.   *
# *                                                      *                                                       *
# *  This Is A Direct Conversion Of Basic Code I Wrote   *  This Tutorial Demonstrates Multiple Viewports In A   *
# *  On The Atari XE Many Years Ago.                     *  Single Window With Both Ortho And Perspective Modes  *
# *                                                      *  Used At The Same Time.  As Well, Two Of The Views    *
# *  It Barely Resembles The Basic Code, But The Idea    *  Have Lighting Enabled, While The Other Two Do Not.   *
# *  Is Exactly The Same.                                *                                                       *
# *                                                      *********************************************************
# *  Branches Are Always Made From An Existing Path      *
# *  So There Should Always Be A Path Through The Maze   *
# *  Although It Could Be Quite Short :)                 *
# *                                                      *
# *  Do Whatever You Want With This Code.  If You Found  *
# *  It Useful Or Have Made Some Nice Changes To It,     *
# *  Send Me An Email: nehe@connect.ab.ca                *
# *                                                      *
# *******************************************************/
#

#
# Ported to PyOpenGL 2.0 by Brian Leair  Feb, 2004
#
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import Numeric
import random
import time					# sleep () pause for 5 seconds when maze is complete
import sys



# *********************** Globals *********************** 
# Python 2.2 defines these directly
try:
	True
except NameError:
	True = 1==1
	False = 1==0


# Some api in the chain is translating the keystrokes to this octal string
# so instead of saying: ESCAPE = 27, we use the following.
ESCAPE = '\033'

# Number of the glut window.
window = 0


# // User Defined Variables

# // General Loops (Used For Seeking)
mx = 0
my = 0
done = False;													# // Flag To Let Us Know When It's Done

width	= 128;													# // Maze Width  (Must Be A Power Of 2)
height	= 128;													# // Maze Height (Must Be A Power Of 2)

tex_data = None													# numarray of unsigned bytes - # // Holds Our RGB Texture Data 

quadric = None													# // The Quadric Object

r = [None ] * 4
g = [None ] * 4
b = [None ] * 4													# // Random Colors (4 Red, 4 Green, 4 Blue)

xrot = 0
yrot = 0
zrot = 0														# // Use For Rotation Of Objects



def UpdateTex(dmx, dmy):
	""" // Update Pixel dmx, dmy On The Texture """
	global tex_data

	tex_data[0+((dmx+(width*dmy))*3)]=255;						# // Set Red Pixel To Full Bright
	tex_data[1+((dmx+(width*dmy))*3)]=255;						# // Set Green Pixel To Full Bright
	tex_data[2+((dmx+(width*dmy))*3)]=255;						# // Set Blue Pixel To Full Bright
	return


def Reset ():
	""" // Reset The Maze, Colors, Start Point, Etc	"""
	global tex_data, r, g, b, mx, my

	# ZeroMemory(tex_data, width * height *3);							// Clear Out The Texture Memory With 0's
	# This creates or array of unsigned bytes for our texture data. All values initialized to 0
	# tex_data = numarray.zeros ((width * height * 3), type="u1")
	tex_data = Numeric.zeros ((width * height * 3), "b")

	# This Will seed the random num stream with current system time.
	random.seed ()

	for loop in xrange (4):												# // Loop So We Can Assign 4 Random Colors
		r[loop]=128 + random.randint (0,127) 							# // Pick A Random Red Color (Bright)
		g[loop]=128 + random.randint (0,127) 							# // Pick A Random Green Color (Bright)
		b[loop]=128 + random.randint (0,127) 							# // Pick A Random Blue Color (Bright)

	mx = random.randint (0, (width/2) - 1) * 2								# // Pick A New Random X Position
	my = random.randint (0, (width/2) - 1) * 2								# // Pick A New Random Y Position
	return



# // Any GL Init Code & User Initialiazation Goes Here
def InitGL(Width, Height):				# We call this right after our OpenGL window is created.
	global tex_data, width, height, quadric

	Reset ()							# // Call Reset To Build Our Initial Texture, Etc.

	glEnable(GL_TEXTURE_2D);									# // Enable Texture Mapping
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, tex_data.tostring ());

	glClearColor (0.0, 0.0, 0.0, 0.0);							# // Black Background
	glClearDepth (1.0);											# // Depth Buffer Setup
	glDepthFunc (GL_LEQUAL);									# // The Type Of Depth Testing
	glEnable (GL_DEPTH_TEST);									# // Enable Depth Testing

	glEnable(GL_COLOR_MATERIAL);								# // Enable Color Material (Allows Us To Tint Textures)

	quadric=gluNewQuadric();									# // Create A Pointer To The Quadric Object
	gluQuadricNormals(quadric, GLU_SMOOTH);						# // Create Smooth Normals 
	gluQuadricTexture(quadric, GL_TRUE);						# // Create Texture Coords

	glEnable(GL_LIGHT0);										# // Enable Light0 (Default GL Light)

	return True;												# // Return TRUE (Initialization Successful)

def Update ():
	""" Solves/builds the maze. """
	global width, height, done, mx, my

	done=True;															# // Set done To True
	for x in xrange (0, width, 2):										# // Loop Through All The Rooms
		for y in xrange (0, height, 2):									# // On X And Y Axis
			if (tex_data[((x+(width*y))*3)]==0):						# // If Current Texture Pixel (Room) Is Blank
				done=False;												# // We Have To Set done To False (Not Finished Yet)

	if (done):															# // If done Is True Then There Were No Unvisited Rooms
		# // Display A Message At The Top Of The Window, Pause For A Bit And Then Start Building A New Maze!
		glutSetWindowTitle ("Lesson 42: Multiple Viewports... 2003 NeHe Productions... Maze Complete!");
		time.sleep (5)
		glutSetWindowTitle ("Lesson 42: Multiple Viewports... 2003 NeHe Productions... Building Maze!");
		Reset();

	# // Check To Make Sure We Are Not Trapped (Nowhere Else To Move)
	if (((mx>(width-4) or tex_data[(((mx+2)+(width*my))*3)]==255)) and ((mx<2 or tex_data[(((mx-2)+(width*my))*3)]==255)) and
		((my>(height-4) or tex_data[((mx+(width*(my+2)))*3)]==255)) and ((my<2 or tex_data[((mx+(width*(my-2)))*3)]==255))):
		while True:												# // If We Are Trapped
			mx = random.randint (0, (width/2) - 1) * 2				# // Pick A New Random X Position
			my = random.randint (0, (height/2) - 1) * 2				# // Pick A New Random Y Position
			if (tex_data[((mx+(width*my))*3)]==0):
				break
				# // Keep Picking A Random Position Until We Find
				# // One That Has Already Been Tagged (Safe Starting Point)

	dir = random.randint (0,3)									# // Pick A Random Direction

	if ((dir==0) and (mx<=(width-4))):									# // If The Direction Is 0 (Right) And We Are Not At The Far Right
		if (tex_data[(((mx+2)+(width*my))*3)]==0):						# // And If The Room To The Right Has Not Already Been Visited
			UpdateTex(mx+1,my);											# // Update The Texture To Show Path Cut Out Between Rooms
			mx+=2;														# // Move To The Right (Room To The Right)

	if ((dir==1) and (my<=(height-4))):									# // If The Direction Is 1 (Down) And We Are Not At The Bottom
		if (tex_data[((mx+(width*(my+2)))*3)]==0):						# // And If The Room Below Has Not Already Been Visited
			UpdateTex(mx,my+1);											# // Update The Texture To Show Path Cut Out Between Rooms
			my+=2;														# // Move Down (Room Below)

	if ((dir==2) and (mx>=2)):											# // If The Direction Is 2 (Left) And We Are Not At The Far Left
		if (tex_data[(((mx-2)+(width*my))*3)]==0):						# // And If The Room To The Left Has Not Already Been Visited
			UpdateTex(mx-1,my);											# // Update The Texture To Show Path Cut Out Between Rooms
			mx-=2;														# // Move To The Left (Room To The Left)

	if ((dir==3) and (my>=2)):											# // If The Direction Is 3 (Up) And We Are Not At The Top
		if (tex_data[((mx+(width*(my-2)))*3)]==0):						# // And If The Room Above Has Not Already Been Visited
			UpdateTex(mx,my-1);											# // Update The Texture To Show Path Cut Out Between Rooms
			my-=2;														# // Move Up (Room Above)

	UpdateTex(mx,my);													# // Update Current Room
	return

last_milliseconds = 0
def DrawGLScene ():
	""" // Our Drawing Routine """
	global xrot, yrot, zrot
	global width, height, tex_data
	global quadric
	global r, g, b
	global last_milliseconds

	cur_milliseconds = glutGet (GLUT_ELAPSED_TIME)
	milliseconds = cur_milliseconds - last_milliseconds
	last_milliseconds = cur_milliseconds
	xrot+=milliseconds * .02;									# // Increase Rotation On The X-Axis
	yrot+=milliseconds * .03;									# // Increase Rotation On The Y-Axis
	zrot+=milliseconds * .015;									# // Increase Rotation On The Z-Axis

	Update ()

	# // Get Window Dimensions
	window_width = glutGet (GLUT_WINDOW_WIDTH)
	window_height = glutGet (GLUT_WINDOW_HEIGHT)

	# // Update Our Texture... This Is The Key To The Programs Speed... Much Faster Than Rebuilding The Texture Each Time
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, 
		GL_RGB, GL_UNSIGNED_BYTE, tex_data.tostring ());

	glClear (GL_COLOR_BUFFER_BIT);										# // Clear Screen

	for loop in xrange (4):												# // Loop To Draw Our 4 Views
		glColor3ub(r[loop],g[loop],b[loop]);							# // Assign Color To Current View

		if (loop==0):													# // If We Are Drawing The First Scene
			# // Set The Viewport To The Top Left.  It Will Take Up Half The Screen Width And Height
			glViewport (0, window_height/2, window_width/2, window_height/2);
			glMatrixMode (GL_PROJECTION);								# // Select The Projection Matrix
			glLoadIdentity ();											# // Reset The Projection Matrix
			# // Set Up Ortho Mode To Fit 1/4 The Screen (Size Of A Viewport)
			gluOrtho2D(0, window_width/2, window_height/2, 0);

		if (loop==1):													# // If We Are Drawing The Second Scene
			# // Set The Viewport To The Top Right.  It Will Take Up Half The Screen Width And Height
			glViewport (window_width/2, window_height/2, window_width/2, window_height/2);
			glMatrixMode (GL_PROJECTION);								# // Select The Projection Matrix
			glLoadIdentity ();											# // Reset The Projection Matrix
			# // Set Up Perspective Mode To Fit 1/4 The Screen (Size Of A Viewport)
			gluPerspective( 45.0, float (width) / float (height), 0.1, 500.0 ); 

		if (loop==2):													# // If We Are Drawing The Third Scene
			# // Set The Viewport To The Bottom Right.  It Will Take Up Half The Screen Width And Height
			glViewport (window_width/2, 0, window_width/2, window_height/2);
			glMatrixMode (GL_PROJECTION);								# // Select The Projection Matrix
			glLoadIdentity ();											# // Reset The Projection Matrix
			# // Set Up Perspective Mode To Fit 1/4 The Screen (Size Of A Viewport)
			gluPerspective( 45.0, float (width) / float(height), 0.1, 500.0 ); 

		if (loop==3):													# // If We Are Drawing The Fourth Scene
			# // Set The Viewport To The Bottom Left.  It Will Take Up Half The Screen Width And Height
			glViewport (0, 0, window_width/2, window_height/2);
			glMatrixMode (GL_PROJECTION);								# // Select The Projection Matrix
			glLoadIdentity ();											# // Reset The Projection Matrix
			# // Set Up Perspective Mode To Fit 1/4 The Screen (Size Of A Viewport)
			gluPerspective( 45.0, float(width) / float(height), 0.1, 500.0 ); 

		glMatrixMode (GL_MODELVIEW);									# // Select The Modelview Matrix
		glLoadIdentity ();												# // Reset The Modelview Matrix

		glClear (GL_DEPTH_BUFFER_BIT);									# // Clear Depth Buffer

		if (loop==0):													# // Are We Drawing The First Image?  (Original Texture... Ortho)
			glBegin(GL_QUADS);											# // Begin Drawing A Single Quad

			# // We Fill The Entire 1/4 Section With A Single Textured Quad.
			glTexCoord2f(1.0, 0.0); 	glVertex2i(window_width/2, 0              );
			glTexCoord2f(0.0, 0.0); 	glVertex2i(0,              0              );
			glTexCoord2f(0.0, 1.0); 	glVertex2i(0,              window_height/2);
			glTexCoord2f(1.0, 1.0); 	glVertex2i(window_width/2, window_height/2);

			glEnd();													# // Done Drawing The Textured Quad

		if (loop==1):													# // Are We Drawing The Second Image?  (3D Texture Mapped Sphere... Perspective)
			glTranslatef(0.0,0.0,-14.0);								# // Move 14 Units Into The Screen

			glRotatef(xrot,1.0,0.0,0.0);								# // Rotate By xrot On The X-Axis
			glRotatef(yrot,0.0,1.0,0.0);								# // Rotate By yrot On The Y-Axis
			glRotatef(zrot,0.0,0.0,1.0);								# // Rotate By zrot On The Z-Axis

			glEnable(GL_LIGHTING);										# // Enable Lighting
			gluSphere(quadric,4.0,32,32);								# // Draw A Sphere
			glDisable(GL_LIGHTING);										# // Disable Lighting
		
		if (loop==2):													# // Are We Drawing The Third Image?  (Texture At An Angle... Perspective)
			glTranslatef(0.0,0.0,-2.0);									# // Move 2 Units Into The Screen
			glRotatef(-45.0,1.0,0.0,0.0);								# // Tilt The Quad Below Back 45 Degrees.
			glRotatef(zrot/1.5,0.0,0.0,1.0);							# // Rotate By zrot/1.5 On The Z-Axis

			glBegin(GL_QUADS);											# // Begin Drawing A Single Quad

			glTexCoord2f(1.0, 1.0); glVertex3f( 1.0,  1.0, 0.0);
			glTexCoord2f(0.0, 1.0); glVertex3f(-1.0,  1.0, 0.0);
			glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0, 0.0);
			glTexCoord2f(1.0, 0.0); glVertex3f( 1.0, -1.0, 0.0);

			glEnd();													# // Done Drawing The Textured Quad


		if (loop==3):													# // Are We Drawing The Fourth Image?  (3D Texture Mapped Cylinder... Perspective)
			glTranslatef(0.0,0.0,-7.0);									# // Move 7 Units Into The Screen
			glRotatef(-xrot/2,1.0,0.0,0.0);								# // Rotate By -xrot/2 On The X-Axis
			glRotatef(-yrot/2,0.0,1.0,0.0);								# // Rotate By -yrot/2 On The Y-Axis
			glRotatef(-zrot/2,0.0,0.0,1.0);								# // Rotate By -zrot/2 On The Z-Axis

			glEnable(GL_LIGHTING);										# // Enable Lighting
			glTranslatef(0.0,0.0,-2.0);									# // Translate -2 On The Z-Axis (To Rotate Cylinder Around The Center, Not An End)
			gluCylinder(quadric,1.5,1.5,4.0,32,16);						# // Draw A Cylinder
			glDisable(GL_LIGHTING);										# // Disable Lighting

	glutSwapBuffers()													# // Flush The GL Rendering Pipeline
	return True

# The function called when our window is resized (which shouldn't happen if you enable fullscreen, below)
def ReSizeGLScene(Width, Height):
	if Height == 0:						# Prevent A Divide By Zero If The Window Is Too Small 
		Height = 1

	glViewport(0, 0, Width, Height)		# Reset The Current Viewport And Perspective Transformation
	glMatrixMode(GL_PROJECTION)
	glLoadIdentity()
	# // field of view, aspect ratio, near and far
	# This will squash and stretch our objects as the window is resized.
	gluPerspective(45.0, float(Width)/float(Height), 0.1, 100.0)

	glMatrixMode(GL_MODELVIEW)
	glLoadIdentity()


# The function called whenever a key is pressed. Note the use of Python tuples to pass in: (key, x, y)  
def keyPressed(*args):
	global window

	# If escape is pressed, kill everything.
	if args[0] == ESCAPE:
		sys.exit ()
	# // Check To See If Spacebar Is Pressed
	if (args[0] == ' '):
		Reset();														# // If So, Call Reset And Start A New Maze

	return

def main():
	global window
	# pass arguments to init
	glutInit(sys.argv)

	# Select type of Display mode:   
	#  Double buffer 
	#  RGBA color
	# Alpha components supported 
	# Depth buffer
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
	
	glutInitWindowSize(1024, 768)
	
	# the window starts at the upper left corner of the screen 
	glutInitWindowPosition(0, 0)
	
	# Okay, like the C version we retain the window id to use when closing, but for those of you new
	# to Python, remember this assignment would make the variable local and not global
	# if it weren't for the global declaration at the start of main.
	window = glutCreateWindow("Lesson 42: Multiple Viewports... 2003 NeHe Productions... Building Maze!");

	# Register the drawing function with glut, BUT in Python land, at least using PyOpenGL, we need to
	# set the function pointer and invoke a function to actually register the callback, otherwise it
	# would be very much like the C version of the code.	
	glutDisplayFunc(DrawGLScene)
	
	# Uncomment this line to get full screen.
	#glutFullScreen()

	# When we are doing nothing, redraw the scene.
	glutIdleFunc(DrawGLScene)
	
	# Register the function called when our window is resized.
	glutReshapeFunc(ReSizeGLScene)
	
	# Register the function called when the keyboard is pressed.  
	# The call setup glutSpecialFunc () is needed to receive 
	# "keyboard function or directional keys." 
	glutKeyboardFunc(keyPressed)
	glutSpecialFunc(keyPressed)

	# We've told Glut the type of window we want, and we've told glut about
	# various functions that we want invoked (idle, resizing, keyboard events).
	# Glut has done the hard work of building up thw windows DC context and 
	# tying in a rendering context, so we are ready to start making immediate mode
	# GL calls.
	# Call to perform inital GL setup (the clear colors, enabling modes, and most releveant -
	# consturct the displays lists for the bitmap font.
	InitGL(640, 480)

	# Start Event Processing Engine	
	glutMainLoop()

# Print message to console, and kick off the main to get it rolling.
if __name__ == "__main__":
	print "Hit ESC key to quit."
	main()


