text: from ..Qt import QtCore, QtGui, QtOpenGL
  from OpenGL.GL import *
  import OpenGL.GL.framebufferobjects as glfbo
  import numpy as np
  from .. import Vector
  from .. import functions as fn
  
  ##Vector = QtGui.QVector3D
  
  ShareWidget = None
  
  class GLViewWidget(QtOpenGL.QGLWidget):
      """
      Basic widget for displaying 3D data
          - Rotation/scale controls
          - Axis/grid display
          - Export options
  
      """
      
      def __init__(self, parent=None):
          global ShareWidget
  
          if ShareWidget is None:
              ## create a dummy widget to allow sharing objects (textures, shaders, etc) between views
              ShareWidget = QtOpenGL.QGLWidget()
              
          QtOpenGL.QGLWidget.__init__(self, parent, ShareWidget)
          
          self.setFocusPolicy(QtCore.Qt.ClickFocus)
          
          self.opts = {
              'center': Vector(0,0,0),  ## will always appear at the center of the widget
              'distance': 10.0,         ## distance of camera from center
              'fov':  60,               ## horizontal field of view in degrees
              'elevation':  30,         ## camera's angle of elevation in degrees
              'azimuth': 45,            ## camera's azimuthal angle in degrees 
                                        ## (rotation around z-axis 0 points along x-axis)
              'viewport': None,         ## glViewport params; None == whole widget
          }
          self.setBackgroundColor('k')
          self.items = []
          self.noRepeatKeys = [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown]
          self.keysPressed = {}
          self.keyTimer = QtCore.QTimer()
          self.keyTimer.timeout.connect(self.evalKeyState)
          
          self.makeCurrent()
  
      def addItem(self, item):
          self.items.append(item)
          if hasattr(item, 'initializeGL'):
              self.makeCurrent()
              try:
                  item.initializeGL()
              except:
                  self.checkOpenGLVersion('Error while adding item %s to GLViewWidget.' % str(item))
                  
          item._setView(self)
          #print "set view", item, self, item.view()
          self.update()
          
      def removeItem(self, item):
          self.items.remove(item)
          item._setView(None)
          self.update()
          
          
      def initializeGL(self):
          self.resizeGL(self.width(), self.height())
          
      def setBackgroundColor(self, *args, **kwds):
          """
          Set the background color of the widget. Accepts the same arguments as
          pg.mkColor().
          """
          self.opts['bgcolor'] = fn.mkColor(*args, **kwds)
          self.update()
          
      def getViewport(self):
          vp = self.opts['viewport']
          if vp is None:
              return (0, 0, self.width(), self.height())
          else:
              return vp
          
      def resizeGL(self, w, h):
          pass
          #glViewport(*self.getViewport())
          #self.update()
  
      def setProjection(self, region=None):
          m = self.projectionMatrix(region)
          glMatrixMode(GL_PROJECTION)
          glLoadIdentity()
          a = np.array(m.copyDataTo()).reshape((4,4))
          glMultMatrixf(a.transpose())
  
      def projectionMatrix(self, region=None):
          # Xw = (Xnd + 1) * width/2 + X
          if region is None:
              region = (0, 0, self.width(), self.height())
          
          x0, y0, w, h = self.getViewport()
          dist = self.opts['distance']
          fov = self.opts['fov']
          nearClip = dist * 0.001
          farClip = dist * 1000.
  
          r = nearClip * np.tan(fov * 0.5 * np.pi / 180.)
          t = r * h / w
  
          # convert screen coordinates (region) to normalized device coordinates
          # Xnd = (Xw - X0) * 2/width - 1
          ## Note that X0 and width in these equations must be the values used in viewport
          left  = r * ((region[0]-x0) * (2.0/w) - 1)
          right = r * ((region[0]+region[2]-x0) * (2.0/w) - 1)
          bottom = t * ((region[1]-y0) * (2.0/h) - 1)
          top    = t * ((region[1]+region[3]-y0) * (2.0/h) - 1)
  
          tr = QtGui.QMatrix4x4()
          tr.frustum(left, right, bottom, top, nearClip, farClip)
          return tr
          
      def setModelview(self):
          glMatrixMode(GL_MODELVIEW)
          glLoadIdentity()
          m = self.viewMatrix()
          a = np.array(m.copyDataTo()).reshape((4,4))
          glMultMatrixf(a.transpose())
          
      def viewMatrix(self):
          tr = QtGui.QMatrix4x4()
          tr.translate( 0.0, 0.0, -self.opts['distance'])
          tr.rotate(self.opts['elevation']-90, 1, 0, 0)
          tr.rotate(self.opts['azimuth']+90, 0, 0, -1)
          center = self.opts['center']
          tr.translate(-center.x(), -center.y(), -center.z())
          return tr
  
      def itemsAt(self, region=None):
          """
          Return a list of the items displayed in the region (x, y, w, h)
          relative to the widget.        
          """
          region = (region[0], self.height()-(region[1]+region[3]), region[2], region[3])
          
          #buf = np.zeros(100000, dtype=np.uint)
          buf = glSelectBuffer(100000)
          try:
              glRenderMode(GL_SELECT)
              glInitNames()
              glPushName(0)
              self._itemNames = {}
              self.paintGL(region=region, useItemNames=True)
              
          finally:
              hits = glRenderMode(GL_RENDER)
              
          items = [(h.near, h.names[0]) for h in hits]
          items.sort(key=lambda i: i[0])
          return [self._itemNames[i[1]] for i in items]
      
      def paintGL(self, region=None, viewport=None, useItemNames=False):
          """
          viewport specifies the arguments to glViewport. If None, then we use self.opts['viewport']
          region specifies the sub-region of self.opts['viewport'] that should be rendered.
          Note that we may use viewport != self.opts['viewport'] when exporting.
          """
          if viewport is None:
              glViewport(*self.getViewport())
          else:
              glViewport(*viewport)
          self.setProjection(region=region)
          self.setModelview()
          bgcolor = self.opts['bgcolor']
          glClearColor(bgcolor.red(), bgcolor.green(), bgcolor.blue(), 1.0)
          glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT )
          self.drawItemTree(useItemNames=useItemNames)
          
      def drawItemTree(self, item=None, useItemNames=False):
          if item is None:
              items = [x for x in self.items if x.parentItem() is None]
          else:
              items = item.childItems()
              items.append(item)
          items.sort(key=lambda a: a.depthValue())
          for i in items:
              if not i.visible():
                  continue
              if i is item:
                  try:
                      glPushAttrib(GL_ALL_ATTRIB_BITS)
                      if useItemNames:
                          glLoadName(i._id)
                          self._itemNames[i._id] = i
                      i.paint()
                  except:
                      from .. import debug
                      debug.printExc()
                      msg = "Error while drawing item %s." % str(item)
                      ver = glGetString(GL_VERSION)
                      if ver is not None:
                          ver = ver.split()[0]
                          if int(ver.split(b'.')[0]) < 2:
                              print(msg + " The original exception is printed above; however, pyqtgraph requires OpenGL version 2.0 or greater for many of its 3D features and your OpenGL version is %s. Installing updated display drivers may resolve this issue." % ver)
                          else:
                              print(msg)
                      
                  finally:
                      glPopAttrib()
              else:
                  glMatrixMode(GL_MODELVIEW)
                  glPushMatrix()
                  try:
                      tr = i.transform()
                      a = np.array(tr.copyDataTo()).reshape((4,4))
                      glMultMatrixf(a.transpose())
                      self.drawItemTree(i, useItemNames=useItemNames)
                  finally:
                      glMatrixMode(GL_MODELVIEW)
                      glPopMatrix()
              
      def setCameraPosition(self, pos=None, distance=None, elevation=None, azimuth=None):
          if distance is not None:
              self.opts['distance'] = distance
          if elevation is not None:
              self.opts['elevation'] = elevation
          if azimuth is not None:
              self.opts['azimuth'] = azimuth
          self.update()
          
          
          
      def cameraPosition(self):
          """Return current position of camera based on center, dist, elevation, and azimuth"""
          center = self.opts['center']
          dist = self.opts['distance']
          elev = self.opts['elevation'] * np.pi/180.
          azim = self.opts['azimuth'] * np.pi/180.
          
          pos = Vector(
              center.x() + dist * np.cos(elev) * np.cos(azim),
              center.y() + dist * np.cos(elev) * np.sin(azim),
              center.z() + dist * np.sin(elev)
          )
          
          return pos
  
      def orbit(self, azim, elev):
          """Orbits the camera around the center position. *azim* and *elev* are given in degrees."""
          self.opts['azimuth'] += azim
          #self.opts['elevation'] += elev
          self.opts['elevation'] = np.clip(self.opts['elevation'] + elev, -90, 90)
          self.update()
          
      def pan(self, dx, dy, dz, relative=False):
          """
          Moves the center (look-at) position while holding the camera in place. 
          
          If relative=True, then the coordinates are interpreted such that x
          if in the global xy plane and points to the right side of the view, y is
          in the global xy plane and orthogonal to x, and z points in the global z
          direction. Distances are scaled roughly such that a value of 1.0 moves
          by one pixel on screen.
          
          """
          if not relative:
              self.opts['center'] += QtGui.QVector3D(dx, dy, dz)
          else:
              cPos = self.cameraPosition()
              cVec = self.opts['center'] - cPos
              dist = cVec.length()  ## distance from camera to center
              xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.)  ## approx. width of view at distance of center point
              xScale = xDist / self.width()
              zVec = QtGui.QVector3D(0,0,1)
              xVec = QtGui.QVector3D.crossProduct(zVec, cVec).normalized()
              yVec = QtGui.QVector3D.crossProduct(xVec, zVec).normalized()
              self.opts['center'] = self.opts['center'] + xVec * xScale * dx + yVec * xScale * dy + zVec * xScale * dz
          self.update()
          
      def pixelSize(self, pos):
          """
          Return the approximate size of a screen pixel at the location pos
          Pos may be a Vector or an (N,3) array of locations
          """
          cam = self.cameraPosition()
          if isinstance(pos, np.ndarray):
              cam = np.array(cam).reshape((1,)*(pos.ndim-1)+(3,))
              dist = ((pos-cam)**2).sum(axis=-1)**0.5
          else:
              dist = (pos-cam).length()
          xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.)
          return xDist / self.width()
          
      def mousePressEvent(self, ev):
          self.mousePos = ev.pos()
          
      def mouseMoveEvent(self, ev):
          diff = ev.pos() - self.mousePos
          self.mousePos = ev.pos()
          
          if ev.buttons() == QtCore.Qt.LeftButton:
              self.orbit(-diff.x(), diff.y())
              #print self.opts['azimuth'], self.opts['elevation']
          elif ev.buttons() == QtCore.Qt.MidButton:
              if (ev.modifiers() & QtCore.Qt.ControlModifier):
                  self.pan(diff.x(), 0, diff.y(), relative=True)
              else:
                  self.pan(diff.x(), diff.y(), 0, relative=True)
          
      def mouseReleaseEvent(self, ev):
          pass
          # Example item selection code:
          #region = (ev.pos().x()-5, ev.pos().y()-5, 10, 10)
          #print(self.itemsAt(region))
          
          ## debugging code: draw the picking region
          #glViewport(*self.getViewport())
          #glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT )
          #region = (region[0], self.height()-(region[1]+region[3]), region[2], region[3])
          #self.paintGL(region=region)
          #self.swapBuffers()
          
          
      def wheelEvent(self, ev):
          if (ev.modifiers() & QtCore.Qt.ControlModifier):
              self.opts['fov'] *= 0.999**ev.delta()
          else:
              self.opts['distance'] *= 0.999**ev.delta()
          self.update()
  
      def keyPressEvent(self, ev):
          if ev.key() in self.noRepeatKeys:
              ev.accept()
              if ev.isAutoRepeat():
                  return
              self.keysPressed[ev.key()] = 1
              self.evalKeyState()
        
      def keyReleaseEvent(self, ev):
          if ev.key() in self.noRepeatKeys:
              ev.accept()
              if ev.isAutoRepeat():
                  return
              try:
                  del self.keysPressed[ev.key()]
              except:
                  self.keysPressed = {}
              self.evalKeyState()
          
      def evalKeyState(self):
          speed = 2.0
          if len(self.keysPressed) > 0:
              for key in self.keysPressed:
                  if key == QtCore.Qt.Key_Right:
                      self.orbit(azim=-speed, elev=0)
                  elif key == QtCore.Qt.Key_Left:
                      self.orbit(azim=speed, elev=0)
                  elif key == QtCore.Qt.Key_Up:
                      self.orbit(azim=0, elev=-speed)
                  elif key == QtCore.Qt.Key_Down:
                      self.orbit(azim=0, elev=speed)
                  elif key == QtCore.Qt.Key_PageUp:
                      pass
                  elif key == QtCore.Qt.Key_PageDown:
                      pass
                  self.keyTimer.start(16)
          else:
              self.keyTimer.stop()
  
      def checkOpenGLVersion(self, msg):
          ## Only to be called from within exception handler.
          ver = glGetString(GL_VERSION).split()[0]
          if int(ver.split('.')[0]) < 2:
              from .. import debug
              pyqtgraph.debug.printExc()
              raise Exception(msg + " The original exception is printed above; however, pyqtgraph requires OpenGL version 2.0 or greater for many of its 3D features and your OpenGL version is %s. Installing updated display drivers may resolve this issue." % ver)
          else:
              raise
              
  
              
      def readQImage(self):
          """
          Read the current buffer pixels out as a QImage.
          """
          w = self.width()
          h = self.height()
          self.repaint()
          pixels = np.empty((h, w, 4), dtype=np.ubyte)
          pixels[:] = 128
          pixels[...,0] = 50
          pixels[...,3] = 255
          
          glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels)
          
          # swap B,R channels for Qt
          tmp = pixels[...,0].copy()
          pixels[...,0] = pixels[...,2]
          pixels[...,2] = tmp
          pixels = pixels[::-1] # flip vertical
          
          img = fn.makeQImage(pixels, transpose=False)
          return img
          
          
      def renderToArray(self, size, format=GL_BGRA, type=GL_UNSIGNED_BYTE, textureSize=1024, padding=256):
          w,h = map(int, size)
          
          self.makeCurrent()
          tex = None
          fb = None
          try:
              output = np.empty((w, h, 4), dtype=np.ubyte)
              fb = glfbo.glGenFramebuffers(1)
              glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, fb )
              
              glEnable(GL_TEXTURE_2D)
              tex = glGenTextures(1)
              glBindTexture(GL_TEXTURE_2D, tex)
              texwidth = textureSize
              data = np.zeros((texwidth,texwidth,4), dtype=np.ubyte)
              
              ## Test texture dimensions first
              glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, None)
              if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0:
                  raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2])
              ## create teture
              glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.transpose((1,0,2)))
              
              self.opts['viewport'] = (0, 0, w, h)  # viewport is the complete image; this ensures that paintGL(region=...) 
                                                    # is interpreted correctly.
              p2 = 2 * padding
              for x in range(-padding, w-padding, texwidth-p2):
                  for y in range(-padding, h-padding, texwidth-p2):
                      x2 = min(x+texwidth, w+padding)
                      y2 = min(y+texwidth, h+padding)
                      w2 = x2-x
                      h2 = y2-y
                      
                      ## render to texture
                      glfbo.glFramebufferTexture2D(glfbo.GL_FRAMEBUFFER, glfbo.GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0)
                      
                      self.paintGL(region=(x, h-y-h2, w2, h2), viewport=(0, 0, w2, h2))  # only render sub-region
                      
                      ## read texture back to array
                      data = glGetTexImage(GL_TEXTURE_2D, 0, format, type)
                      data = np.fromstring(data, dtype=np.ubyte).reshape(texwidth,texwidth,4).transpose(1,0,2)[:, ::-1]
                      output[x+padding:x2-padding, y+padding:y2-padding] = data[padding:w2-padding, -(h2-padding):-padding]
                      
          finally:
              self.opts['viewport'] = None
              glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, 0)
              glBindTexture(GL_TEXTURE_2D, 0)
              if tex is not None:
                  glDeleteTextures([tex])
              if fb is not None:
                  glfbo.glDeleteFramebuffers([fb])
              
          return output
          
          
          
  