text: #!/usr/bin/python3
  
  """
  Use directional lighting and ambient lighting to implement
  a primitive lighting model. The model is loaded from a 
  wavefront obj that was created with Blender.
  """
  
  import sys
  import os.path
  sys.path.append('/'.join(os.path.dirname(os.path.abspath(__file__)).split('/')[:-1]))
  
  from genericgl import TestApplication
  from genericgl import RotatableCanvas
  from genericgl import info
  from genericgl import Wavefront
  from genericgl.testapplication import _TestApplication
  
  import array
  import json
  
  from PyQt5.QtWidgets import *
  from PyQt5.QtGui import *
  from PyQt5.QtCore import *
  
  class TestCanvas(RotatableCanvas):
  
      def __init__(self):
  
          self.suzanne = Wavefront("../objs/stripped_base_mesh.obj")
  
          with open("vertex.glsl","r") as f:
              self.vertexShaderSource = f.read()
  
          with open("fragment.glsl","r") as f:
              self.fragmentShaderSource = f.read()
  
          if self.vertexShaderSource is None:
              raise Exception("Could not load the source for the vertex shader")
  
          if self.fragmentShaderSource is None:
              raise Exception("Could not load the source for the fragment shader")
  
          # Mirror since image coordinates are reversed compared to GL ones. 
          self.skinImage = QImage("../textures/skin.png").mirrored(False, True)
  
          # Use an initial scale assuming width = height (should always be overwritten
          # in the resizeGL method below)
          self.currentScaling = QVector4D(1.0, 1.0, 1.0, 1.0)
  
          self.diffuseStrength = 0.9
          self.ambientStrength = 0.1
          self.specularStrength = 0.1
          self.specularHardness = 4
  
          super(TestCanvas,self).__init__()
  
      def setupGL(self):
  
          self.gl.glEnable(self.gl.GL_TEXTURE_2D)
  
          self.program = QOpenGLShaderProgram(self.context())
          info("PROGRAM",self.program)
  
          # addShaderFromSourceCode() only returns a bool telling whether everything went well
  
          if self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, self.vertexShaderSource):
              info("VERTEX SHADER","Managed to load and parse the vertex shader")
  
          if self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, self.fragmentShaderSource):
              info("FRAGMENT SHADER","Managed to load and parse the fragment shader")
  
          # Compile and bind the shader program to the current context
          self.program.link()
          self.program.bind()
  
          self.skinTexture = QOpenGLTexture(self.skinImage)
          #self.skinTexture.setMinificationFilter(QOpenGLTexture.Nearest);
          #self.skinTexture.setMagnificationFilter(QOpenGLTexture.Linear);
  
          # Find the uniform location for controling the viewport scale of the vertex positions
          self.viewportScaling = self.program.uniformLocation("viewportScaling")
  
          # Find the uniform location for controling the rotation of the object
          self.objectRotation = self.program.uniformLocation("objectRotation")
  
          # Find light setting uniforms
          self.diffuseStrengthUniform = self.program.uniformLocation("diffuseStrength")
          self.ambientStrengthUniform = self.program.uniformLocation("ambientStrength")
          self.specularStrengthUniform = self.program.uniformLocation("specularStrength")
          self.specularHardnessUniform = self.program.uniformLocation("specularHardness")
  
          # This returns a numpy array with the shape [ [XYZNNNTT] [XYZNNNTT] ... ]
          self.vertices2d = self.suzanne.getVertexAndNormalAndTexCoArray()
  
          print(self.vertices2d)
  
          # ... so we need to flatten it to an 1d array
          self.vertices = self.vertices2d.flatten()
  
          # Number of elements in the flattened array
          self.verticesLength = self.vertices.size
  
          # Each vertex is specified with with six values (xyznnntt)
          self.arrayCellsPerVertex = 8
  
          # Size in bytes for each vertex specification (self.vertices.itemsize is the size in bytes
          # of a single array cell)
          self.vertexSpecificationSize = self.vertices.itemsize * self.arrayCellsPerVertex
  
          # In bytes, where in a vertex specification does the normal data start? (it starts after 
          # x, y, z.. i.e after 3 array cells)
          self.normalBytesOffset = self.vertices.itemsize * 3
  
          # In bytes, where in a vertex specification does the normal data start? (it starts after 
          # x, y, z.. i.e after 3 array cells)
          self.textureBytesOffset = self.vertices.itemsize * 6
  
          # How many bytes are there in between vertex location specifications
          self.vertexStride = self.vertices.itemsize * self.arrayCellsPerVertex
  
          # What GL datatype should we tell the buffer that we're using? This is necessary, since it
          # might be different on 32-bit and 64-bit machines
  
          self.glDataType = None # let it crash if it isn't float or double
  
          if self.vertices.itemsize == 4:
              self.glDataType = self.gl.GL_FLOAT
  
          if self.vertices.itemsize == 8:
              self.glDataType = self.gl.GL_DOUBLE
  
          # Total size in bytes for entire array
          self.verticesDataLength = self.vertices.nbytes
  
          # 2d Array with vertex indices that make up faces
          self.indices2d = self.suzanne.getFaceArray()
          
          # Flattened version suitable for drawElements
          self.indices = self.indices2d.flatten().tolist()
  
          # Number of elements in the index array. 
          self.numberOfVertices = self.indices2d.size
  
          # Start specifying the Vertex Array Object (VAO). 
          self.suzanneVAO = QOpenGLVertexArrayObject()
          self.suzanneVAO.create()
          self.suzanneVAO.bind()
   
          # Create a VBO for holding vertex info (both positions and normals)
          self.verticesBuffer = QOpenGLBuffer(QOpenGLBuffer.VertexBuffer)
          self.verticesBuffer.create()
          self.verticesBuffer.bind()
          self.verticesBuffer.setUsagePattern(QOpenGLBuffer.StaticDraw)
          self.verticesBuffer.allocate(self.vertices.tobytes(), self.verticesDataLength)
  
          # Here we specify that there is a program attribute that should get data that is sent
          # to it (by glDraw* operations), and in what form the data will arrive. 
          self.program.enableAttributeArray( self.program.attributeLocation("somePosition") )
          self.program.setAttributeBuffer( self.program.attributeLocation("somePosition"), self.glDataType, 0, 3, self.vertexStride )
          
          # Say that the inputNormal attribute should be read from the same array, that it should take three values at a 
          # time (the normal vector), and that they are of type GL_FLOAT. The same byte offset as for the position info is used, but
          # we now also specify that the normal information starts at normalBytesOffset bytes into each vertex specification
          self.program.enableAttributeArray( self.program.attributeLocation("inputNormal") )
          self.program.setAttributeBuffer( self.program.attributeLocation("inputNormal"), self.glDataType, self.normalBytesOffset, 3, self.vertexStride )
  
          # Say that the textureCoordinate attribute should be read from the same array, that it should take two values at a 
          # time (XY texture coord), and that they are of type GL_FLOAT. The same byte offset as for the position info is used, but
          # we now also specify that the normal information starts at textureBytesOffset bytes into each vertex specification
          self.program.enableAttributeArray( self.program.attributeLocation("textureCoordinate") )
          self.program.setAttributeBuffer( self.program.attributeLocation("textureCoordinate"), self.glDataType, self.textureBytesOffset, 2, self.vertexStride )
           
          # Once we have set up everything related to the VBOs, we can release that and the 
          # related VAO.
          self.verticesBuffer.release()
          self.suzanneVAO.release()
       
          # Release the program until we need to actually draw something. 
          self.program.release()
  
          self.gl.glClearColor(0.1, 0.1, 0.1, 1.0)
  
          self.dumpGLLogMessages("setupGL()")
  
      def paintGL(self):
          self.gl.glClear(self.gl.GL_COLOR_BUFFER_BIT | self.gl.GL_DEPTH_BUFFER_BIT)
          
          # We re-enable the program and use it for all draw operations (both VAOs use
          # this program)
          self.program.bind()
  
          # Set current rotation as a uniform
          self.currentRotation = QVector3D(self.xRot / 16, self.yRot / 16, self.zRot / 16)
          self.program.setUniformValue(self.objectRotation, self.currentRotation)
  
          # Set light uniforms
          self.program.setUniformValue(self.diffuseStrengthUniform, self.diffuseStrength)
          self.program.setUniformValue(self.ambientStrengthUniform, self.ambientStrength)
          self.program.setUniformValue(self.specularStrengthUniform, self.specularStrength)
          self.program.setUniformValue(self.specularHardnessUniform, float(self.specularHardness))
  
          # Before painting we set the scaling "uniform" parameter in the vertex shader.
          # This will be used to scale the vertex positions. 
          self.program.setUniformValue(self.viewportScaling, self.currentScaling)
   
          # Activate the VAO
          self.suzanneVAO.bind()
  
          self.skinTexture.bind()
  
          # Draw the VAO. It will remember which VBO was specified for it. Note the use
          # of glDrawElements rather than glDrawArrays. 
          self.gl.glDrawElements(self.gl.GL_TRIANGLES, self.numberOfVertices, self.gl.GL_UNSIGNED_SHORT, self.indices)
  
          self.skinTexture.release()
  
          # Release the VAO
          self.suzanneVAO.release()
  
          # Release the program
          self.program.release()
  
          self.dumpGLLogMessages("paintGL()")
  
      def resizeGL(self, width, height):
          scaleX = 1.0
          scaleY = 1.0
          scaleZ = 1.0 # Will always be 1.0 since we don't scale depth yet
          scaleW = 1.0 # A global scale factor. Higher = smaller object on screen. 
  
          if width > height:
              scaleX = height / width
          else:
              scaleY = width / height
  
          self.currentScaling = QVector4D(scaleX, scaleY, scaleZ, scaleW)
  
          # Redraw since we changed the value of the scaling uniform
          self.update()
  
      def closeGL(self):
          # We need to explicitly destroy VAOs, VBOs and programs. Otherwise we'll get a 
          # segfault or another similar crash.
          self.skinTexture.destroy()
          self.suzanneVAO.destroy()
          self.verticesBuffer.destroy()
          del self.program
          del self.skinTexture
  
      def updateParameters(self, diffuseStrength, ambientStrength, specularStrength, specularHardness):
  
          self.diffuseStrength = diffuseStrength
          self.ambientStrength = ambientStrength
          self.specularStrength = specularStrength
          self.specularHardness = specularHardness
  
          self.update()
  
  
  class SettingsWidget(QWidget):
  
      def __init__(self, mainWidget):
  
          super(SettingsWidget, self).__init__()
  
          self.mainWidget = mainWidget
  
          self.layout = QVBoxLayout(self)
  
          self.diffuseStrength = 0.8
          self.ambientStrength = 0.2
          self.specularStrength = 0.1
          self.specularHardness = 6
  
          self.diffuseStrengthLabel = QLabel("-")
          self.ambientStrengthLabel = QLabel("-")
          self.specularStrengthLabel = QLabel("-")
          self.specularHardnessLabel = QLabel("-")
          
          self.diffuseStrengthSlider = QScrollBar(Qt.Horizontal)
          self.ambientStrengthSlider = QScrollBar(Qt.Horizontal)
          self.specularStrengthSlider = QScrollBar(Qt.Horizontal)
          self.specularHardnessSlider = QScrollBar(Qt.Horizontal)
  
          self.diffuseStrengthSlider.setMinimum(0)
          self.diffuseStrengthSlider.setMaximum(10)
          self.diffuseStrengthSlider.setPageStep(1)
          self.diffuseStrengthSlider.setSingleStep(1)
          self.diffuseStrengthSlider.setValue(int(10 * self.diffuseStrength))
          self.diffuseStrengthSlider.valueChanged.connect(self.sliderMoved)
  
          self.ambientStrengthSlider.setMinimum(0)
          self.ambientStrengthSlider.setMaximum(10)
          self.ambientStrengthSlider.setPageStep(1)
          self.ambientStrengthSlider.setSingleStep(1)
          self.ambientStrengthSlider.setValue(int(10 * self.ambientStrength))
          self.ambientStrengthSlider.valueChanged.connect(self.sliderMoved)
  
          self.specularStrengthSlider.setMinimum(0)
          self.specularStrengthSlider.setMaximum(10)
          self.specularStrengthSlider.setPageStep(1)
          self.specularStrengthSlider.setSingleStep(1)
          self.specularStrengthSlider.setValue(int(10 * self.specularStrength))
          self.specularStrengthSlider.valueChanged.connect(self.sliderMoved)
  
          self.specularHardnessSlider.setMinimum(1)
          self.specularHardnessSlider.setMaximum(64)
          self.specularHardnessSlider.setPageStep(1)
          self.specularHardnessSlider.setSingleStep(1)
          self.specularHardnessSlider.setValue(self.specularHardness)
          self.specularHardnessSlider.valueChanged.connect(self.sliderMoved)
  
          self.layout.addWidget(self.diffuseStrengthLabel)
          self.layout.addWidget(self.diffuseStrengthSlider)
          self.layout.addWidget(self.ambientStrengthLabel)
          self.layout.addWidget(self.ambientStrengthSlider)
          self.layout.addWidget(self.specularStrengthLabel)
          self.layout.addWidget(self.specularStrengthSlider)
          self.layout.addWidget(self.specularHardnessLabel)
          self.layout.addWidget(self.specularHardnessSlider)
  
          self.layout.addStretch()    
          self.resize(600, 600);
  
          self.updateLabels()
  
      # Override if necessary                                                                                        
      def minimumSizeHint(self):                                                                                     
          return QSize(200, 200)                                                                                       
  
      def sizeHint(self):                                                                                            
          return QSize(600, 600)
  
      def updateLabels(self):
          self.diffuseStrengthLabel.setText("diffuseStrength: " + str(self.diffuseStrength))
          self.ambientStrengthLabel.setText("<br />ambientStrength: " + str(self.ambientStrength))
          self.specularStrengthLabel.setText("<br />specularStrength: " + str(self.specularStrength))
          self.specularHardnessLabel.setText("<br />specularHardness: " + str(self.specularHardness))
  
      def sliderMoved(self):
  
          self.diffuseStrength = self.diffuseStrengthSlider.value() / 10
          self.ambientStrength = self.ambientStrengthSlider.value() / 10
          self.specularStrength = self.specularStrengthSlider.value() / 10
          self.specularHardness = self.specularHardnessSlider.value()
  
          self.updateLabels()
  
          self.mainWidget.updateParameters(self.diffuseStrength, self.ambientStrength, self.specularStrength, self.specularHardness)
  
  
  class SettingsApplication(_TestApplication):
  
      def __init__(self, args, glWidget = QOpenGLWidget):
  
          super(SettingsApplication, self).__init__(args, glWidget)
  
          self.settingsWidget = SettingsWidget(self.mainWidget)
          self.layout.addWidget(self.settingsWidget)
          self.mainWin.resize(1200,600)
  
  
  app = SettingsApplication(sys.argv, TestCanvas)
  app.exec_()
  del app
  sys.exit()
  
  