# pyNewTorus.py 
# Credit to George K. Francis for the excellent original program

from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
from math import *
import sys

import psyco
psyco.full()

# define some new trig functions here for
# later use in the program.  This is NEAT STUFF!
def C(u): return cos(u*0.01745)
def S(u): return sin(u*0.01745)

global wd
global ht
global mouseX
global mouseY
global aff 
global nrange
nrange = 3.0
pi = 3.1415926

aff = [1.0, 0.0, 0.0, 0.0,
       0.0, 1.0, 0.0, 0.0,
       0.0, 0.0, 1.0, 0.0,
       0.0, 0.0, 0.0, 1.0]

def drawvert(th,ta):
   
   # parametric sphere equations
   #n0= C(th)*C(ta)
   #n1= S(th)*C(ta)
   #n2=       S(ta)

   # Crosscap
   #n0= C(th)*S(2*ta)
   #n1= S(th)*S(2*ta)
   #n2= C(ta)*C(ta) - C(th)*C(th)*S(ta)*S(ta)

   # Klein Bottle! 
   #n0 = C(th)*(1 + S(ta)*C(th/2) - (S(2*ta)*S(th/2))/2) 
   #n1 = S(th)*(1 + S(ta)*C(th/2) - (S(2*ta)*S(th/2))/2) 
   #n2 = S(th/2)*S(ta) + (C(th/2)*S(2*ta))/2
   
   # Steiner's Roman Surface
   #n0 = C(ta)*C(ta)*S(2*th)
   #n1 = S(th)*S(2*ta)
   #n2 = C(th)*S(2*ta)
   
   # Another torus
   #n0 = (1 + 0.5*C(ta))*C(th)
   #n1 = (1 + 0.5*C(ta))*S(th)
   #n2 = 0.5*S(ta)
   
   # George's Etruscan Venus
   n0 = 1.2*C(2*th)*C(ta) + S(ta)*C(th)
   n1 = 1.2*S(2*th)*C(ta) - S(ta)*S(th)
   n2 = 2*C(ta)
   
   # figure 8... beautiful!
   #n0 = C(th)*(1 + S(ta)*C(th) - S(2*ta)*S(th)/2)
   #n1 = S(th)*(1 + S(ta)*C(th) - S(2*ta)*S(th)/2)
   #n2 = S(th)*S(ta) + C(th)*S(2*ta)/2
   
   glColor3f(S(th),C(ta),S(th*ta)) 
   
   # torus!
   #glVertex3f(C(th)+.5*n0, S(th)+.5*n1, .5*n2)
   
   # sphere
   glVertex3f(n0, n1, n2)

def drawtor():
   for th in range(0 ,348,15):
      glBegin(GL_TRIANGLE_STRIP)
      for ta in range (0,348, 15):
         drawvert(th,ta); drawvert(th+8,ta) 
      glEnd() 

#assign initial window and mouse settings
wd = 300
ht = 300
mouseX = wd/2
mouseY = ht/2

brake = 128.0 

def display():
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
   glMatrixMode(GL_MODELVIEW)
   glLoadIdentity()
   glMultMatrixf(aff)
   drawtor()
   glutSwapBuffers()

#typical keyboard callback 
def keyboard(key, x, y):
   if key == chr(27) or key == 'q':
     sys.exit(0)
   glutPostRedisplay()

#adjust to resizing of the window
def reshape(width, height):
   global wd
   global ht
   glClearColor(0.0, 0.0, 0.0, 0.0)
   if height == 0:
      height = 1
   wd = width
   ht = height
   glViewport(0,0,wd,ht)
   glMatrixMode(GL_PROJECTION)
   glLoadIdentity()
   if wd<=ht:
      glOrtho(-nrange,nrange,-nrange*ht/wd,nrange*ht/wd,-nrange,nrange)
   else:
      glOrtho(-nrange*wd/ht,nrange*wd/ht,-nrange,nrange,-nrange,nrange)
   glMatrixMode(GL_MODELVIEW)
   glLoadIdentity()

#Note that we must declare the globals again
def chaptrack():
   global mouseX
   global mouseY
   global wd
   global ht
   global aff
   dx = (mouseX-wd/2)/brake  
   dy = (mouseY-ht/2)/brake
   glMatrixMode(GL_TEXTURE)
   glPushMatrix()
   glLoadIdentity()
   glRotatef(dx,0,1.0,0.0)
   glRotatef(dy,1.0,0.0,0.0)
   glMultMatrixf(aff)
   aff = glGetFloatv(GL_TEXTURE_MATRIX)
   glPopMatrix()

#traditional idle
def idle():
   chaptrack()
   glutPostRedisplay()

#ditto traditional mousemotion
def mousemotion(x,y):
   global mouseX
   global mouseY
   mouseX = x
   mouseY = y

def init():
    glEnable(GL_DEPTH_TEST)
    glShadeModel(GL_SMOOTH)

#Traditional main subroutine
def main() :
   global wd
   global ht
   glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE)
   glutInitWindowPosition(50, 50)
   glutInitWindowSize(wd, ht)
   glutInit()
   glutCreateWindow("illiTorus")
   glutKeyboardFunc(keyboard)
   glutDisplayFunc(display)
   glutIdleFunc(idle)
   glutReshapeFunc(reshape)
   glutPassiveMotionFunc(mousemotion)
   
   init()
   glutMainLoop()

main()

