from pygame.locals import *
from OpenGL.GL import *
import pygame
import math
import numpy
from ctypes import *

(WIDTH,HEIGHT) = 640, 480

def createCube((xi,yi,zi),(xf,yf,zf),color=(1.0,1.0,1.0,1.0)):
	"""
	Creates a cube.
	"""
	vertices = numpy.zeros(36, [("position", numpy.float32, 3),
								("color"   , numpy.float32, 4),
								("normal"  , numpy.float32, 3)])
	vertices["position"] = [
		(xi, yi, zi), (xi, yi, zf), (xi, yf, zf), (xi, yf, zf), (xi, yf, zi), (xi, yi, zi),

		(xf, yi, zi), (xf, yf, zf), (xf, yi, zf), (xf, yf, zf), (xf, yi, zi), (xf, yf, zi),

		(xf, yi, zi), (xf, yi, zf), (xi, yi, zf), (xi, yi, zf), (xi, yi, zi), (xf, yi, zi),

		(xf, yf, zi), (xi, yf, zf), (xf, yf, zf), (xi, yf, zf), (xf, yf, zi), (xi, yf, zi),

		(xi, yi, zi), (xi, yf, zi), (xf, yf, zi), (xf, yf, zi), (xf, yi, zi), (xi, yi, zi),

		(xi, yi, zf), (xf, yf, zf), (xi, yf, zf), (xf, yf, zf), (xi, yi, zf), (xf, yi, zf)]

	vertices["color"][:] = color
	vertices["normal"][ 0: 6] = (+1.0,  0.0,  0.0)
	vertices["normal"][ 6:12] = (-1.0,  0.0,  0.0)
	vertices["normal"][12:18] = ( 0.0, +1.0,  0.0)
	vertices["normal"][18:24] = ( 0.0, -1.0,  0.0)
	vertices["normal"][24:30] = ( 0.0,  0.0, +1.0)
	vertices["normal"][30:36] = ( 0.0,  0.0, -1.0)

	return vertices

def perspectiveGL(fovy, aspect, near, far):
	"""
	Replaces gluPerspective
	"""
	fH = math.tan(fovy/360.0) * math.pi * near
	fW = fH * aspect
	glFrustum(-fW,fW,-fH,fH,near,far)

def init():
	pygame.init()
	screen = pygame.display.set_mode((WIDTH,HEIGHT), OPENGL | DOUBLEBUF, 24)
	pygame.mouse.set_visible(False)
	glClearColor (0.0, 0.5, 0.5, 1.0)

	glEnable(GL_BLEND)
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
	glHint (GL_LINE_SMOOTH_HINT, GL_NICEST)
	glEnable(GL_CULL_FACE)
	glEnable(GL_DEPTH_TEST)
	glDepthFunc(GL_LESS)
	return screen

def reshape(screen):
	WIDTH,HEIGHT = screen.get_size()

	glViewport(0, 0, WIDTH,HEIGHT)
	glMatrixMode(GL_PROJECTION)
	glLoadIdentity()
	perspectiveGL(90.0,WIDTH/float(HEIGHT),0.1,100.0)
	glMatrixMode(GL_MODELVIEW)
	glLoadIdentity()

def compileShader(vertex,fragment):
	# request program and shader slots from GPU
	program = glCreateProgram()
	vertexShader = glCreateShader(GL_VERTEX_SHADER)
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER)

	#set shaders source
	glShaderSource(vertexShader,vertex)
	glShaderSource(fragmentShader,fragment)

	#compile shaders
	glCompileShader(vertexShader)
	glCompileShader(fragmentShader)

	#attach shader objects to the program
	glAttachShader(program,vertexShader)
	glAttachShader(program,fragmentShader)

	#build program
	glLinkProgram(program)

	#get rid of shaders (needed no more)
	glDetachShader(program,vertexShader)
	glDetachShader(program,fragmentShader)

	return program

def attribute(loc,size,type,normalized,stride,offset):
	"""
	attribute(loc,size,type,normalized,stride,offset)

	loc: Specifies the index of the generic vertex attribute to be modified.
	size: Specifies the number of components per generic vertex attribute.
		  Must be 1, 2, 3, 4.
	type: Specifies the data type of each component in the array.
		  GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT,
		  GL_UNSIGNED_INT, GL_FLOAT and GL_DOUBLE.
	normalized: Specifies whether fixed-point data values should be normalized (True) or
		  converted directly as fixed-point values (False)
	stride: Specifies the byte offset between consecutive generic vertex attributes.
	offset: Specifies a offset of the first component of the first generic vertex attribute in the
			array in the data store of the buffer currently bound to the GL_ARRAY_BUFFER target.
	"""
	# https://www.opengl.org/sdk/docs/man/html/glVertexAttribPointer.xhtml
	glEnableVertexAttribArray(loc)

	glVertexAttribPointer(loc, size, type, normalized, stride, ctypes.c_void_p(offset))
	
	glDisableVertexAttribArray(loc)

def uniform(loc,type,*value):
	"""
	uniform(name,type, value ... )

	loc: Specifies the index of the generic vertex attribute to be modified.
	type: 1f, 2f, 3f, 4f, 1i, 2i, 3i, 4i, 22f, 23f, 24f, 33f, 32f, 34f, 44f, 42f, 43f
	*value:
	For the scalar commands:
		[0][1][2][3] Specifies the new values to be used for the specified uniform variable.
					 ( v0, v1, v2, v3 )
	For the matrix array commands:
		[0] Specifies the number of elements that are to be modified.
			This should be 1 if the targeted uniform variable is not an array, and 1 or more if it is an array.
			( count )
		[1] Specifies whether to transpose the matrix as the values are loaded into the uniform variable.
			( transpose )
		[2] Specifies a pointer to an array of count values that will be used to update the specified uniform variable.
			( value )
	"""
	# http://pyopengl.sourceforge.net/documentation/manual-3.0/glUniform.html
	
	if type == '1f':
		glUniform1f(loc, value[0])
	elif type == '2f':
		glUniform2f(loc, value[0], value[1])
	elif type == '3f':
		glUniform3f(loc, value[0], value[1], value[2])
	elif type == '4f':
		glUniform4f(loc, value[0], value[1], value[2], value[3])
	
	elif type == '1i':
		glUniform1i(loc, value[0])
	elif type == '2i':
		glUniform2i(loc, value[0], value[1])
	elif type == '3i':
		glUniform2i(loc, value[0], value[1], value[2])
	elif type == '4i':
		glUniform2i(loc, value[0], value[1], value[2], value[3])

	# for matrices:  location , count , transpose , value
	elif type == '22f':
		glUniformMatrix2fv(loc,value[0],value[1],value[2])
	elif type == '23f':
		glUniformMatrix2x3fv(loc,value[0],value[1],value[2])
	elif type == '24f':
		glUniformMatrix2x4fv(loc,value[0],value[1],value[2])
	elif type == '33f':
		glUniformMatrix3fv(loc,value[0],value[1],value[2])
	elif type == '32f':
		glUniformMatrix3x2fv(loc,value[0],value[1],value[2])
	elif type == '34f':
		glUniformMatrix3x4fv(loc,value[0],value[1],value[2])
	elif type == '44f':
		glUniformMatrix4fv(loc,value[0],value[1],value[2])
	elif type == '42f':
		glUniformMatrix4x2fv(loc,value[0],value[1],value[2])
	elif type == '43f':
		glUniformMatrix4x3fv(loc,value[0],value[1],value[2])
	
	else:
		raise ValueError("Unknown type " + type)

def asignAttributes(vertex,buffer,position_loc,color_loc,normal_loc):
	glBindBuffer(GL_ARRAY_BUFFER, buffer)
	glBufferData(GL_ARRAY_BUFFER, vertex, GL_DYNAMIC_DRAW)

	# The size in bytes of a FLOAT is 4 bytes
	stride = 4 * (3+4+3)
	offset = 0
	attribute(position_loc,3,GL_FLOAT,False,stride,offset)
	offset = 4*3
	attribute(color_loc,3,GL_FLOAT,False,stride,offset)
	offset = 4*(3+4)
	attribute(normal_loc,3,GL_FLOAT,False,stride,offset)

# EL VERTEX Y FRAGMENT SHADER CAMBIARON SOLO UN POCO
# PERO EL CAMBIO ES NOTABLE

VERTEX = """

attribute vec3 position;
attribute vec4 color;
attribute vec3 normal;

varying vec4 myColor;
varying vec3 myNormal;
varying vec3 myPosition;

uniform vec3 lightPos;

void main(){
	myColor = color;
	myNormal = normal;
	myPosition = position;
	gl_Position = gl_ModelViewProjectionMatrix * vec4(myPosition,1.0);
} 
"""

FRAGMENT = """

varying vec4 myColor;
varying vec3 myNormal;
varying vec3 myPosition;

uniform vec3 lightPos;
uniform vec3 lightCol;

void main(){
    float blend = 0.5;
    float d = max(dot(myNormal,normalize(lightPos-myPosition)),0.0);
    vec3 color = blend*lightCol + (1-blend)*myColor;
	gl_FragColor = vec4(color*d,myColor.a);
}
"""

if __name__ == '__main__':
	screen = init()
	reshape(screen)

	# CREATE THE PROGRAM
	program = compileShader(VERTEX,FRAGMENT)

	glUseProgram(program)

	#We set the location of our attributes
	position_loc = 0
	color_loc = 1
	normal_loc = 2
	glBindAttribLocation(program,position_loc,"position")
	glBindAttribLocation(program,color_loc,"color")
	glBindAttribLocation(program,normal_loc,"normal")

	# CREATE THE DATA BUFFERS
	vertex = createCube((-5,-5,-5),(5,5,5),(1,1,1,1.0))

	buffer = glGenBuffers(1)

	# BIND THE DATA
	asignAttributes(vertex,buffer,0,1,2)

	# CREATE THE UNIFORMS
	# PARA PRODUCIR ESTOS EFECTOS NUEVOS, AGREGAMOS DOS UNIFORMS
	uni_loc = glGetUniformLocation(program, "lightPos")
	uni_col_loc = glGetUniformLocation(program, "lightCol")
	angle = 0
	R = 20
	color = 0

	run = True
	while run:
		for event in pygame.event.get():
			if event.type == QUIT:
				run = False

		# DRAW ON SCREEN
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
		glLoadIdentity()
		
		glTranslate(0,0,-20)
		glRotate(-angle*10,1,0.5,1)
		
		# ACTUALIZAMOS CADA UNIFORM CON NUEVOS VALORES
		
		r = ( (color)     % 10 ) / 10.0
		g = ( (color/10)  % 10 ) / 10.0
		b = ( (color/100) % 10 ) / 10.0
		
		uniform(uni_col_loc,'3f',r,g,b)
		uniform(uni_loc,'3f',R*math.cos(angle),0,R*math.sin(angle))
		
		angle += 0.025
		color += 0.01
		color %= 1000
		
		glEnableVertexAttribArray(position_loc)
		glEnableVertexAttribArray(color_loc)
		glEnableVertexAttribArray(normal_loc)
		
		glDrawArrays(GL_TRIANGLES,0,len(vertex))

		glDisableVertexAttribArray(position_loc)
		glDisableVertexAttribArray(color_loc)
		glDisableVertexAttribArray(normal_loc)
		pygame.display.flip()
