#!/usr/local/bin/python2.5

# This is statement is required by the build system to query build info
if __name__ == '__build__':
	raise Exception

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLE import *
from OpenGL.GLUT import *
from math import *
from Numeric import *
from random import *
import string


def loadKnot(filename):
	f = open(filename)
	curves = []
	points = []

	for line in f.readlines():
		if string.lower(line[:9]) == 'component':
			if len(points):
				curves.append(points)
				points = []
		else:
			try:
				points.append(array(map(float, line.split())))
			except:
				pass

	if len(points):
		curves.append(points)
		points = []

	f.close()

	return curves


def get_a(n):
	a = 2.0*ones(((n+3)/2-(n%2),), Float)
	a[0] = 1.0
	if not n % 2:
		a[-1] == 1.0

	return a/n


def eval_pos(s, n, a):
	return dot(a, cos(2*pi*s/n*array(range(len(a)), Float)))


def eval_loop(s, v, a):
	global n, S
	S = s
	n = len(v)
	b = fromfunction(lambda i, j: cos(2*pi/n*(S-i)*j), (n, len(a)))
	return matrixmultiply(matrixmultiply(b, a), v)


def refine(points):
	#~ import time
	#~ n = len(points)
	#~ a = get_a(n)
	#~ print a
	#~ s = map(lambda x: [float(x), 0.0], range(n))
	#~ s[0][1] = 1.0

	#~ now = time.time()
	#~ pos = 0
	#~ while pos < len(s):
		#~ next_pos = (pos + 1) % len(s)

		#~ if next_pos:
			#~ next_s = s[next_pos][0]
		#~ else:
			#~ next_s = len(points)

		#~ sample = (s[pos][0] + next_s)*0.5
		#~ exact = eval_pos(sample, n, a)
		#~ approx = (s[pos][1] + s[next_pos][1])*0.5
		#~ if abs(exact - approx) > 0.01:
			#~ s.insert(pos + 1, [sample, exact])
		#~ else:
			#~ pos = pos + 1
	#~ print time.time() - now
	#~ now = time.time()

	#~ p = []
	#~ for sample, pos in s:
		#~ p.append(eval_loop(sample, points, a))

	#~ print time.time() - now
	#~ return p
	p = []
	for i in range(len(points)):
		for j in range(10):
			s = j/10.0
			x = array([0.0, 0.0, 0.0])
			for k in range(-1, 3):
				m = 1.0
				for l in range(-1, 3):
					if l != k:
						m = m*(s-l)/(k-l)
				x = x + m*points[(k+i) % len(points)]
			p.append(x)
	return array(p)


class knotView:

	def __init__(self, filename, width = 640, height = 480):	
		self.knot = loadKnot(filename)

		self.colors = []
		for i in range(len(self.knot)):
			self.colors.append([random(), random(), random()])

		self.list = None
		self.phi = 0
		self.theta = 0

		min_x = [sys.maxint]*3
		max_x = [-sys.maxint]*3

		for loop in self.knot:
			for x in loop:
				min_x = map(min, min_x, x)
				max_x = map(max, max_x, x)

		center = map(lambda y, z: (y + z)*0.5, min_x, max_x)

		self.radius = 0.0

		for loop in self.knot:
#			print loop
			for x in loop:
				radius2 = 0.0
				for i in range(3):
					x[i] = x[i] - center[i]
					radius2 = radius2 + x[i]**2
				self.radius = max(self.radius, sqrt(radius2))

#		map(refine, self.knot)
		self.knot = map(refine, self.knot)

		glutInitWindowSize(width, height)
		self.win = glutCreateWindow(filename)

		glutDisplayFunc(self.onDisplay)
		glutReshapeFunc(self.onReshape)
		glutSpecialFunc(self.onSpecial)

		glClearColor(1.0, 0.0, 0.0, 0.0)
		glClearDepth(1.0)
		glDepthFunc(GL_LESS)
		glEnable(GL_DEPTH_TEST)
		glShadeModel(GL_SMOOTH)
		glEnable(GL_LIGHTING)
		glEnable(GL_LIGHT0)
		glEnable(GL_CULL_FACE)
	
		glMatrixMode(GL_PROJECTION)
		glLoadIdentity()

		gluPerspective(45.0, float(width)/max(float(height), 1.0), 0.1, 100.0)
		glTranslatef(0, 0.0, -30.0)
	
#		glEnable(GL_AUTO_NORMAL)
#		glEnable(GL_NORMALIZE)

		glMatrixMode(GL_MODELVIEW)


	def onDisplay(self):
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
		glShadeModel(GL_SMOOTH)

		glLoadIdentity()
		glRotatef(self.theta, -1, 0, 0)
		glRotatef(self.phi, 0, 0, 1)

		if self.list is None:
			self.list = glGenLists(1)
			glNewList(self.list, GL_COMPILE_AND_EXECUTE)

			gleSetNumSides(50)
			gleSetJoinStyle(TUBE_JN_ANGLE | TUBE_CONTOUR_CLOSED | TUBE_NORM_PATH_EDGE )# | TUBE_NORM_FACET)

			glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50)
			glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, ( 1.0, 1.0, 1.0, 1.0 ))
			for i in range(len(self.knot)):
				glMaterialfv(GL_FRONT, GL_DIFFUSE, self.colors[i] + [1])
				loop = self.knot[i]
				glePolyCylinder(loop, None, self.radius/15.0)
			glEndList()
		else:
			glCallList(self.list)

		glutSwapBuffers()


	def onReshape(self, width, height):
		glViewport(0, 0, width, height)
		glMatrixMode(GL_PROJECTION)
		glLoadIdentity()
		gluPerspective(45.0, float(width)/max(float(height), 1.0), 0.1, 100.0)
		glTranslatef(0, 0.0, -30.0)
		glMatrixMode(GL_MODELVIEW)


	def onSpecial(self, key, x, y):
		if key == GLUT_KEY_LEFT:
			self.phi = (self.phi + 358) % 360
			glutPostRedisplay()
		elif key == GLUT_KEY_RIGHT:
			self.phi = (self.phi + 2) % 360
			glutPostRedisplay()
		elif key == GLUT_KEY_UP:
			self.theta = (self.theta + 358) % 360
			glutPostRedisplay()
		elif key == GLUT_KEY_DOWN:
			self.theta = (self.theta + 2) % 360
			glutPostRedisplay()
		
if __name__ == '__main__':
	import sys
	args = glutInit(sys.argv)
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)
	views = []
	if len(args) == 1:
		args.append('8.10')                # use append()
	for file in args[1:]:                  # skip first parameter
		views.append(knotView(file))
	glutMainLoop()

