text: # coding=utf-8
  """
  Daniel Calderon, CC3501, 2019-2
  Simple Shaders
  """
  
  from OpenGL.GL import *
  import OpenGL.GL.shaders
  import numpy as np
  from PIL import Image
  
  import basic_shapes as bs
  
  
  # We will use 32 bits data, so we have 4 bytes
  # 1 byte = 8 bits
  SIZE_IN_BYTES = 4
  
  
  # A simple class container to reference a shape on GPU memory
  class GPUShape:
      def __init__(self):
          self.vao = 0
          self.vbo = 0
          self.ebo = 0
          self.texture = 0
          self.size = 0
  
  
  def textureSimpleSetup(texture, imgName, wrapMode, filterMode):
       # wrapMode: GL_REPEAT, GL_CLAMP_TO_EDGE
       # filterMode: GL_LINEAR, GL_NEAREST
  
      glBindTexture(GL_TEXTURE_2D, texture)
  
      # texture wrapping params
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode)
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode)
  
      # texture filtering params
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterMode)
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterMode)
  
      image = Image.open(imgName)
      img_data = np.array(list(image.getdata()), np.uint8)
  
      if image.mode == "RGB":
          internalFormat = GL_RGB
          format = GL_RGB
      elif image.mode == "RGBA":
          internalFormat = GL_RGBA
          format = GL_RGBA
      else:
          print("Image mode not supported.")
          raise Exception()
  
      glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, image.size[0], image.size[1], 0, format, GL_UNSIGNED_BYTE, img_data)
  
  
  def toGPUShape(shape, wrapMode=None, filterMode=None):
      assert isinstance(shape, bs.Shape)
  
      vertexData = np.array(shape.vertices, dtype=np.float32)
      indices = np.array(shape.indices, dtype=np.uint32)
  
      # Here the new shape will be stored
      gpuShape = GPUShape()
  
      gpuShape.size = len(shape.indices)
      gpuShape.vao = glGenVertexArrays(1)
      gpuShape.vbo = glGenBuffers(1)
      gpuShape.ebo = glGenBuffers(1)
  
      # Vertex data must be attached to a Vertex Buffer Object (VBO)
      glBindBuffer(GL_ARRAY_BUFFER, gpuShape.vbo)
      glBufferData(GL_ARRAY_BUFFER, len(vertexData) * SIZE_IN_BYTES, vertexData, GL_STATIC_DRAW)
  
      # Connections among vertices are stored in the Elements Buffer Object (EBO)
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gpuShape.ebo)
      glBufferData(GL_ELEMENT_ARRAY_BUFFER, len(indices) * SIZE_IN_BYTES, indices, GL_STATIC_DRAW)
  
      if shape.textureFileName != None:
          assert wrapMode != None and filterMode != None
          
          gpuShape.texture = glGenTextures(1)
          textureSimpleSetup(gpuShape.texture, shape.textureFileName, wrapMode, filterMode)
  
      return gpuShape
  
  
  
  class SimpleShaderProgram:
  
      def __init__(self):
  
          vertex_shader = """
              #version 130
  
              in vec3 position;
              in vec3 color;
  
              out vec3 newColor;
              void main()
              {
                  gl_Position = vec4(position, 1.0f);
                  newColor = color;
              }
              """
  
          fragment_shader = """
              #version 130
              in vec3 newColor;
  
              out vec4 outColor;
              void main()
              {
                  outColor = vec4(newColor, 1.0f);
              }
              """
  
          self.shaderProgram = OpenGL.GL.shaders.compileProgram(
              OpenGL.GL.shaders.compileShader(vertex_shader, GL_VERTEX_SHADER),
              OpenGL.GL.shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER))
  
  
      def drawShape(self, shape, mode=GL_TRIANGLES):
          assert isinstance(shape, GPUShape)
  
          # Binding the proper buffers
          glBindVertexArray(shape.vao)
          glBindBuffer(GL_ARRAY_BUFFER, shape.vbo)
          glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, shape.ebo)
  
          # 3d vertices + rgb color specification => 3*4 + 3*4 = 24 bytes
          position = glGetAttribLocation(self.shaderProgram, "position")
          glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(0))
          glEnableVertexAttribArray(position)
          
          color = glGetAttribLocation(self.shaderProgram, "color")
          glVertexAttribPointer(color, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(12))
          glEnableVertexAttribArray(color)
  
          # Render the active element buffer with the active shader program
          glDrawElements(mode, shape.size, GL_UNSIGNED_INT, None)
  
  
  class SimpleTextureShaderProgram:
  
      def __init__(self):
  
          vertex_shader = """
              #version 130
  
              in vec3 position;
              in vec2 texCoords;
  
              out vec2 outTexCoords;
  
              void main()
              {
                  gl_Position = vec4(position, 1.0f);
                  outTexCoords = texCoords;
              }
              """
  
          fragment_shader = """
              #version 130
  
              in vec2 outTexCoords;
  
              out vec4 outColor;
  
              uniform sampler2D samplerTex;
  
              void main()
              {
                  outColor = texture(samplerTex, outTexCoords);
              }
              """
  
          self.shaderProgram = OpenGL.GL.shaders.compileProgram(
              OpenGL.GL.shaders.compileShader(vertex_shader, GL_VERTEX_SHADER),
              OpenGL.GL.shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER))
  
  
      def drawShape(self, shape, mode=GL_TRIANGLES):
          assert isinstance(shape, GPUShape)
  
          # Binding the proper buffers
          glBindVertexArray(shape.vao)
          glBindBuffer(GL_ARRAY_BUFFER, shape.vbo)
          glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, shape.ebo)
          glBindTexture(GL_TEXTURE_2D, shape.texture)
  
          # 3d vertices + 2d texture coordinates => 3*4 + 2*4 = 20 bytes
          position = glGetAttribLocation(self.shaderProgram, "position")
          glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(0))
          glEnableVertexAttribArray(position)
          
          texCoords = glGetAttribLocation(self.shaderProgram, "texCoords")
          glVertexAttribPointer(texCoords, 2, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(12))
          glEnableVertexAttribArray(texCoords)
  
          # Render the active element buffer with the active shader program
          glDrawElements(mode, shape.size, GL_UNSIGNED_INT, None)
  
  
  class SimpleTransformShaderProgram:
  
      def __init__(self):
  
          vertex_shader = """
              #version 130
              
              uniform mat4 transform;
  
              in vec3 position;
              in vec3 color;
  
              out vec3 newColor;
  
              void main()
              {
                  gl_Position = transform * vec4(position, 1.0f);
                  newColor = color;
              }
              """
  
          fragment_shader = """
              #version 130
              in vec3 newColor;
  
              out vec4 outColor;
  
              void main()
              {
                  outColor = vec4(newColor, 1.0f);
              }
              """
  
          self.shaderProgram = OpenGL.GL.shaders.compileProgram(
              OpenGL.GL.shaders.compileShader(vertex_shader, OpenGL.GL.GL_VERTEX_SHADER),
              OpenGL.GL.shaders.compileShader(fragment_shader, OpenGL.GL.GL_FRAGMENT_SHADER))
  
  
      def drawShape(self, shape, mode=GL_TRIANGLES):
          assert isinstance(shape, GPUShape)
  
          # Binding the proper buffers
          glBindVertexArray(shape.vao)
          glBindBuffer(GL_ARRAY_BUFFER, shape.vbo)
          glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, shape.ebo)
  
          # 3d vertices + rgb color specification => 3*4 + 3*4 = 24 bytes
          position = glGetAttribLocation(self.shaderProgram, "position")
          glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(0))
          glEnableVertexAttribArray(position)
          
          color = glGetAttribLocation(self.shaderProgram, "color")
          glVertexAttribPointer(color, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(12))
          glEnableVertexAttribArray(color)
  
          # Render the active element buffer with the active shader program
          glDrawElements(mode, shape.size, GL_UNSIGNED_INT, None)
  
  
  class SimpleTextureTransformShaderProgram:
  
      def __init__(self):
  
          vertex_shader = """
              #version 130
  
              uniform mat4 transform;
  
              in vec3 position;
              in vec2 texCoords;
  
              out vec2 outTexCoords;
  
              void main()
              {
                  gl_Position = transform * vec4(position, 1.0f);
                  outTexCoords = texCoords;
              }
              """
  
          fragment_shader = """
              #version 130
  
              in vec2 outTexCoords;
  
              out vec4 outColor;
  
              uniform sampler2D samplerTex;
  
              void main()
              {
                  outColor = texture(samplerTex, outTexCoords);
              }
              """
  
          self.shaderProgram = OpenGL.GL.shaders.compileProgram(
              OpenGL.GL.shaders.compileShader(vertex_shader, GL_VERTEX_SHADER),
              OpenGL.GL.shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER))
  
  
      def drawShape(self, shape, mode=GL_TRIANGLES):
          assert isinstance(shape, GPUShape)
  
          # Binding the proper buffers
          glBindVertexArray(shape.vao)
          glBindBuffer(GL_ARRAY_BUFFER, shape.vbo)
          glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, shape.ebo)
          glBindTexture(GL_TEXTURE_2D, shape.texture)
  
          # 3d vertices + 2d texture coordinates => 3*4 + 2*4 = 20 bytes
          position = glGetAttribLocation(self.shaderProgram, "position")
          glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(0))
          glEnableVertexAttribArray(position)
          
          texCoords = glGetAttribLocation(self.shaderProgram, "texCoords")
          glVertexAttribPointer(texCoords, 2, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(12))
          glEnableVertexAttribArray(texCoords)
  
          # Render the active element buffer with the active shader program
          glDrawElements(mode, shape.size, GL_UNSIGNED_INT, None)
  
  
  class SimpleModelViewProjectionShaderProgram:
  
      def __init__(self):
  
          vertex_shader = """
              #version 130
              
              uniform mat4 projection;
              uniform mat4 view;
              uniform mat4 model;
  
              in vec3 position;
              in vec3 color;
  
              out vec3 newColor;
              void main()
              {
                  gl_Position = projection * view * model * vec4(position, 1.0f);
                  newColor = color;
              }
              """
  
          fragment_shader = """
              #version 130
              in vec3 newColor;
  
              out vec4 outColor;
              void main()
              {
                  outColor = vec4(newColor, 1.0f);
              }
              """
  
          self.shaderProgram = OpenGL.GL.shaders.compileProgram(
              OpenGL.GL.shaders.compileShader(vertex_shader, OpenGL.GL.GL_VERTEX_SHADER),
              OpenGL.GL.shaders.compileShader(fragment_shader, OpenGL.GL.GL_FRAGMENT_SHADER))
  
  
      def drawShape(self, shape, mode=GL_TRIANGLES):
          assert isinstance(shape, GPUShape)
  
          # Binding the proper buffers
          glBindVertexArray(shape.vao)
          glBindBuffer(GL_ARRAY_BUFFER, shape.vbo)
          glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, shape.ebo)
  
          # 3d vertices + rgb color specification => 3*4 + 3*4 = 24 bytes
          position = glGetAttribLocation(self.shaderProgram, "position")
          glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(0))
          glEnableVertexAttribArray(position)
          
          color = glGetAttribLocation(self.shaderProgram, "color")
          glVertexAttribPointer(color, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(12))
          glEnableVertexAttribArray(color)
  
          # Render the active element buffer with the active shader program
          glDrawElements(mode, shape.size, GL_UNSIGNED_INT, None)
  
  
  class SimpleTextureModelViewProjectionShaderProgram:
  
      def __init__(self):
  
          vertex_shader = """
              #version 130
              
              uniform mat4 projection;
              uniform mat4 view;
              uniform mat4 model;
  
              in vec3 position;
              in vec2 texCoords;
  
              out vec2 outTexCoords;
  
              void main()
              {
                  gl_Position = projection * view * model * vec4(position, 1.0f);
                  outTexCoords = texCoords;
              }
              """
  
          fragment_shader = """
              #version 130
  
              uniform sampler2D samplerTex;
  
              in vec2 outTexCoords;
  
              out vec4 outColor;
  
              void main()
              {
                  outColor = texture(samplerTex, outTexCoords);
              }
              """
  
          self.shaderProgram = OpenGL.GL.shaders.compileProgram(
              OpenGL.GL.shaders.compileShader(vertex_shader, OpenGL.GL.GL_VERTEX_SHADER),
              OpenGL.GL.shaders.compileShader(fragment_shader, OpenGL.GL.GL_FRAGMENT_SHADER))
  
  
      def drawShape(self, shape, mode=GL_TRIANGLES):
          assert isinstance(shape, GPUShape)
  
          # Binding the proper buffers
          glBindVertexArray(shape.vao)
          glBindBuffer(GL_ARRAY_BUFFER, shape.vbo)
          glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, shape.ebo)
          glBindTexture(GL_TEXTURE_2D, shape.texture)
  
          # 3d vertices + 2d texture coordinates => 3*4 + 2*4 = 20 bytes
          position = glGetAttribLocation(self.shaderProgram, "position")
          glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(0))
          glEnableVertexAttribArray(position)
          
          texCoords = glGetAttribLocation(self.shaderProgram, "texCoords")
          glVertexAttribPointer(texCoords, 2, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(12))
          glEnableVertexAttribArray(texCoords)
  
          # Render the active element buffer with the active shader program
          glDrawElements(mode, shape.size, GL_UNSIGNED_INT, None)
  
  