Jaswant P
Jaswant P

Reputation: 83

Can anyone provide an example usage of PyQt5's OpenGL methods?

What is the right way to use setAttributeBuffer? What should the offset be measured in? Bytes? or the number of elements? None seem to work when using multiple vertex attributes. What exactly to provide for the indices argument in glDrawElements? Why do I get 0 returned from QOpenGLShaderProgram.attributeLocation? How to get an attribute location from shader? I'm linking to an example code that should draw a grid. Without the usage of the colour attribute, it works. But as soon as I enable the colour attribute, I get nothing on the screen. Can anyone please point out what is wrong or at least provide an example usage? The calls to setAttributeBuffer can be found in GraphicsObject.allocateVertices()

# PyQT/OpenGL example
import ctypes
import os
import time
from functools import partial
from math import copysign

import numpy
from PyQt5 import QtGui, QtWidgets, QtCore

VPORTMIN = -8.0
VPORTMAX = 8.0
DAMPENER = 0.1


SHADERPATH = './'


def parseShader(file: str, program: QtGui.QOpenGLShaderProgram):
    def readStrings(inFile: file):
        source = ''
        lines = []
        lastpos = inFile.tell()
        for data in iter(inFile.readline, ''):
            if "#shader" in data:
                break
            elif data == eof:
                break
            lines.append(data)
            lastpos = inFile.tell()

        inFile.seek(lastpos)
        return source.join(lines)

    file = open(file, 'r')
    file.readlines()
    eof = file.tell()  # get location of EOF character.
    file.seek(0, 0)  # go back to file beginning.
    vs = ''
    fs = ''
    while True:
        line = file.readline()
        if file.tell() == eof:
            # Reached EOF.
            break
        elif not line:
            continue
        elif line == "#shader vertex\n":
            vs = readStrings(file)
        elif line == "#shader fragment\n":
            fs = readStrings(file)
    program.addShaderFromSourceCode(QtGui.QOpenGLShader.Vertex, vs)
    program.addShaderFromSourceCode(QtGui.QOpenGLShader.Fragment, fs)
    if not program.link():
        log = program.log()
        print(log)
        return False
    else:
        return True


class Window(QtWidgets.QMainWindow):
    def __init__(self, app):
        super().__init__()

        self.app = app
        self.app.mainwindow = self
        self.glviewPort = GLViewport(parent=self)
        self.glviewPort.setMinimumSize(600, 600)
        self.glviewPort.setMouseTracking(True)
        self.glviewPort.grabKeyboard()
        self.setCentralWidget(self.glviewPort)

    def closeEvent(self, event: QtGui.QCloseEvent):
        self.glviewPort.flush()
        return super().closeEvent(event)


class GraphicsObject:
    def __init__(self, name=None):
        self.name = name
        self.vao = QtGui.QOpenGLVertexArrayObject()
        self.vbo = QtGui.QOpenGLBuffer(QtGui.QOpenGLBuffer.VertexBuffer)
        self.ibo = QtGui.QOpenGLBuffer(QtGui.QOpenGLBuffer.IndexBuffer)
        self.vertices = []
        self.indices = []
        self.dtype = None
        self.modelMatrix = QtGui.QMatrix4x4()
        self.modelMatrix.setToIdentity()
        self.viewMatrix = QtGui.QMatrix4x4()
        self.viewMatrix.setToIdentity()
        self.projMatrix = QtGui.QMatrix4x4()
        self.mvpMatrix = self.projMatrix * self.viewMatrix * self.modelMatrix
        self.shaderProgram = None
        self.vertexPosIdx = 0
        self.vertexColorIdx = 0
        self.u_MVPidx = 0
        self.primType = None
        self.count = 0
        self.color = QtGui.QVector4D(1.0, 1.0, 1.0, 0.0)
        self.rendererId = None
        self.blendStatus = False
        self.lineSmoothStat = False
        self.linewidth = 1.0
        self.usagePattern = QtGui.QOpenGLBuffer.StaticDraw
        self.dirty = False
        self.allowzoom = True

    def createObjects(self):
        self.shaderProgram = QtGui.QOpenGLShaderProgram()
        self.shaderProgram.create()
        self.vao.create()
        self.vbo.create()
        self.ibo.create()

    def buildShader(self):
        if not parseShader(os.path.join(SHADERPATH, 'graphshader.glsl'), self.shaderProgram):
            print("Failed to compile shader!")
            sys.exit(1)
        else:
            self.shaderProgram.bind()
            self._cacheUniforms()

    def _cacheUniforms(self):
        self.vertexPosIdx = self.shaderProgram.attributeLocation("position")
        self.vertexColorIdx = self.shaderProgram.attributeLocation("color")
        self.u_MVPidx = self.shaderProgram.uniformLocation("u_mvp")
        print(self.u_MVPidx)

    def bindAll(self):
        self.shaderProgram.bind()
        self.vao.bind()
        self.vbo.bind()
        self.ibo.bind()

    def setDatatype(self, dtype):
        """
        Sets datatype.
        Args:
            dtype: dtype.

        Returns: None

        """
        self.dtype = dtype

    def setUsagePattern(self, pattern):
        self.usagePattern = pattern

    def allocateVertices(self, vertices: list):
        self.vbo.setUsagePattern(self.usagePattern)
        self.ibo.setUsagePattern(self.usagePattern)
        self.vertices = numpy.array(vertices, dtype=numpy.float32)
        if self.vbo.bufferId():
            self.vbo.allocate(self.vertices.ctypes.data_as(ctypes.POINTER(ctypes.c_void_p)).contents,
                              sys.getsizeof(self.vertices))
            self.shaderProgram.enableAttributeArray(self.vertexPosIdx)
            self.shaderProgram.setAttributeBuffer(self.vertexPosIdx,  # Location of attribute in vertex shader.
                                                  self.dtype,  # data type of vertices.
                                                  0,  # Start location in vertices buffer.
                                                  3,  # No. of components per vertex in vertices buffer.
                                                  7 * 4)  # stride, number of bytes b/w consecutive vertices.
            self.shaderProgram.enableAttributeArray(self.vertexColorIdx)
            self.shaderProgram.setAttributeBuffer(self.vertexColorIdx,  # Location of attribute in vertex shader.
                                                 self.dtype,  # data type of vertices.
                                                 3 * 4,  # Start location to color attributes in vertices buffer.
                                                 4,  # No. of components per vertex in vertices buffer.
                                                 7 * 4)  # stride, number of bytes b/w consecutive vertices.

        self.dirty = True

    def allocateIndices(self, indices):
        self.ibo.bind()
        self.indices = numpy.array(indices, dtype=numpy.uint32)
        if self.ibo.bufferId():
            self.ibo.allocate(self.indices.ctypes.data_as(ctypes.POINTER(ctypes.c_void_p)).contents,
                              sys.getsizeof(self.indices))

    def unbindAll(self):
        self.shaderProgram.release()
        self.vao.release()
        self.vbo.release()

    def destroyBuffers(self):
        self.vao.destroy()
        self.vbo.destroy()
        self.ibo.destroy()

    def destroy(self):
        self.unbindAll()
        self.destroyShader()
        self.destroyBuffers()

    def destroyShader(self):
        self.shaderProgram.removeAllShaders()

    def setAllowZoom(self, state):
        """
        Allow/block camera zoom.
        Args:
            state: bool

        Returns: None

        """
        self.allowzoom = state

    def setPrimitives(self, primType):
        self.primType = primType

    def setCount(self, count):
        self.count = count

    def setColor(self, r, g, b, a):
        """
        Sets color
        Args:
            r: red 0-1
            g: green 0-1
            b: blue 0-1
            a: alpha 0-1

        Returns:

        """
        self.color = QtGui.QVector4D(r, g, b, a)

    def setBlend(self, status):
        """
        Enables blend bit.
        Args:
            status: bool

        Returns: None

        """
        self.blendStatus = status

    def setLineSmooth(self, status):
        """
        Enables line smooth.
        Args:
            status: bool

        Returns: None

        """
        self.lineSmoothStat = status

    def setLineWidth(self, w: float):
        """
        Sets a line width.
        Args:
            w: float; clamped to 0.1-1

        Returns: None

        """
        self.linewidth = w
        if copysign(1, w - 1.0) > 0:
            # provided width > 1
            self.linewidth = w
        elif copysign(1, w - 0.0) < 0:
            # provided width < 0
            self.linewidth = 0.1

    def setTranslation(self, x, y, z):
        """
        Translates model by x, y, z.
        Args:
            x: translate along x. in units of dx
            y: translate along y. in units of dy
            z: translate along z. in units of dz

        Returns: None

        """
        self.modelMatrix.translate(QtGui.QVector3D(x, y, z))
        self.mvpMatrix = self.projMatrix * self.viewMatrix * self.modelMatrix

    def setScale(self, sx=1.0, sy=1.0, sz=1.0):
        """
        Scales model by sx, sy, sz. Default is 1.0, i.e, no scale.
        Args:
            sx: scale along x
            sy: scale along y
            sz: scale along z

        Returns: None

        """
        self.modelMatrix.scale(QtGui.QVector3D(sx, sy, sz))
        self.mvpMatrix = self.projMatrix * self.viewMatrix * self.modelMatrix

    def setRotation(self, angle, x=0.0, y=0.0, z=1.0):
        """
        Rotates model by angle degrees around axis (x, y, z). Default axis points out of screen. z+ve.
        angle > 0, CW rotation
        angle < 0, CCW rotation
        Args:
            angle: degrees
            x: axis direction
            y: axis direction
            z: axis direction

        Returns: None

        """
        self.modelMatrix.rotate(angle, QtGui.QVector3D(x, y, z))
        self.mvpMatrix = self.projMatrix * self.viewMatrix * self.modelMatrix

    def setViewMat(self, mat: QtGui.QMatrix4x4):
        if not self.allowzoom:
            data = mat.data()
            data[0] = 1.0
            data[5] = 1.0
            data[10] = 1.0
            self.viewMatrix = QtGui.QMatrix4x4(data).transposed()
        else:
            self.viewMatrix = mat
            self.mvpMatrix = self.projMatrix * self.viewMatrix * self.modelMatrix

    def setProjMat(self, mat: QtGui.QMatrix4x4):
        self.projMatrix = mat
        self.mvpMatrix = self.projMatrix * self.viewMatrix * self.modelMatrix

    def drawCall(self, gl):
        if self.blendStatus:
            gl.glEnable(gl.GL_BLEND)
            gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_DST_ALPHA)
        if self.lineSmoothStat:
            gl.glEnable(gl.GL_LINE_SMOOTH)
        if 1.0 - self.linewidth > 1.0e-5:
            gl.glLineWidth(self.linewidth)
        self.shaderProgram.bind()
        self.vao.bind()
        self.ibo.bind()
        self.shaderProgram.setUniformValue(self.u_MVPidx, self.mvpMatrix)
        gl.glDrawElements(self.primType, len(self.indices), gl.GL_UNSIGNED_INT, None)
        self.shaderProgram.release()
        self.vao.release()
        self.ibo.release()
        if self.blendStatus:
            gl.glDisable(gl.GL_BLEND)
        if self.lineSmoothStat:
            gl.glDisable(gl.GL_LINE_SMOOTH)
        if 1.0 - self.linewidth > 1.0e-5:
            gl.glLineWidth(1.0)

    def updateContents(self, offset, data: list, count):
        self.bindAll()
        data = numpy.array(data, dtype=numpy.float32)
        self.vbo.write(offset, data.ctypes.data_as(ctypes.POINTER(ctypes.c_void_p)).contents, count)
        self.unbindAll()
        self.dirty = True

    def resetTransform(self):
        self.modelMatrix.setToIdentity()
        self.mvpMatrix = self.projMatrix * self.viewMatrix * self.modelMatrix


class Camera:
    """
    An abstract camera class that processes input and calculates the vectors and matrices for use in OpenGL.
    """
    UP = QtCore.Qt.Key_Up
    DOWN = QtCore.Qt.Key_Down
    LEFT = QtCore.Qt.Key_Left
    RIGHT = QtCore.Qt.Key_Right
    movementSpeed = 2.5
    xmin = VPORTMIN
    xmax = VPORTMAX
    ymin = VPORTMIN
    ymax = VPORTMAX
    lastPos = QtGui.QVector3D()
    lowerleft = QtCore.QPointF()
    upperright = QtCore.QPointF()

    def __init__(self, position: QtGui.QVector3D, front: QtGui.QVector3D, up: QtGui.QVector3D):
        """
        Constructor. Initialize a camera at origin, looking at center with up as specified.
        Args:
            position: position of camera in world space, i.e, the space with all other objects.
            front: the camera looks in the front direction.
            up: the camera when upright has it's up axis pointed in specified 'up' direction.
        """
        self.position = position
        self.front = front
        self.up = up
        self.position = position
        self.direction = front
        self.up = up
        self.right = QtGui.QVector3D.crossProduct(self.direction, self.up)
        self.front = self.position + self.direction
        self.view = QtGui.QMatrix4x4()
        self.zoomIncrement = 0.0
        self.view.lookAt(self.position, self.front, self.up)
        self.projection = QtGui.QMatrix4x4()
        self.projection.ortho(VPORTMIN, VPORTMAX, VPORTMIN, VPORTMAX, 0.0, 100.0)

    def getViewMatrix(self):
        """
        Return a view matrix
        Returns: QtGui.QMatrix4x4

        """
        return self.view

    def getProjMatrix(self):
        """
        Return a projection matrix
        Returns: QtGui.QMatrix4x4

        """
        return self.projection

    def processMousePan(self, posNdc: QtGui.QVector3D, eventType: QtGui.QMouseEvent.Type):
        """
        Pan the viewport in direction of mouse move.
        Args
            pos (QtGui.QVector3D): in viewport co-ordinates.
            eventType (QtGui.QMouseEvent.Type): type of mouse event.

        Returns: None

        """
        posProj = self.projection.inverted()[0] * posNdc
        posMV = self.view.inverted()[0] * posProj
        if eventType == QtGui.QMouseEvent.MouseButtonPress:
            self.lastPos = posMV
        elif eventType == QtGui.QMouseEvent.MouseMove:
            offset = posMV - self.lastPos
            offset.setZ(0.0)
            self.view.translate(offset)
            self.position = self.position - offset
            self.front = self.position + self.direction
            self.view.setToIdentity()
            self.view.lookAt(self.position, self.front, self.up)
        elif eventType == QtGui.QMouseEvent.MouseButtonRelease:
            pass

    def processKbd(self, direction, deltatime):
        """
        Process up,down,left,right key inputs.
        Returns: None

        """
        velocity = self.movementSpeed * deltatime
        if direction == self.UP:
            # Move everything down
            dx = QtGui.QVector3D(0.0, 0.0, 0.0)
            dy = QtGui.QVector3D(0.0, velocity, 0.0)
        elif direction == self.DOWN:
            # Move everything up
            dx = QtGui.QVector3D(0.0, 0.0, 0.0)
            dy = QtGui.QVector3D(0.0, -velocity, 0.0)
        elif direction == self.RIGHT:
            # Move everything left
            dx = QtGui.QVector3D(velocity, 0.0, 0.0)
            dy = QtGui.QVector3D(0.0, 0.0, 0.0)
        elif direction == self.LEFT:
            # Move everything right
            dx = QtGui.QVector3D(-velocity, 0.0, 0.0)
            dy = QtGui.QVector3D(0.0, 0.0, 0.0)
        else:
            return
        self.position = self.position + dx + dy
        self.front = self.position + self.direction
        self.view.setToIdentity()
        self.view.lookAt(self.position, self.front, self.up)

    def fitView(self, xmin, xmax, ymin, ymax):

        self.projection.setToIdentity()
        self.projection.ortho(xmin, xmax, ymin, ymax, -0.1, 100.0)

    def processResize(self, w: float, h: float, keepAspectRatio=True):
        if keepAspectRatio:
            aspectRatio = w / h
            xSpan = 1.0
            ySpan = 1.0
            if aspectRatio >= 1:
                # width >= height
                xSpan *= aspectRatio
            else:
                # height > width
                ySpan = xSpan / aspectRatio
            self.xmin = VPORTMIN * xSpan
            self.xmax = VPORTMAX * xSpan
            self.ymin = VPORTMIN * ySpan
            self.ymax = VPORTMAX * ySpan
        self.fitView(self.xmin, self.xmax, self.ymin, self.ymax)

    def processWheel(self, factor, w, h, deltatime):
        aspectRatio = float(w) / float(h)
        xSpan = 1.0
        ySpan = 1.0
        if aspectRatio >= 1:
            # width >= height
            xSpan *= aspectRatio
        else:
            # height > width
            ySpan = xSpan / aspectRatio
        zDampener = min(self.xmax - self.xmin, self.ymax - self.ymin)
        # Damp zoom so that it's slow when far away. Also consider frame latency.
        # exponential function from [https://www.geogebra.org/m/eBHzJyKt]
        zDampener = 0.8 * deltatime * 1.00 ** zDampener
        if copysign(1, (factor - 1.0)) < 0:
            # zoom out
            self.xmin -= zDampener * xSpan
            self.xmax += zDampener * xSpan
            self.ymin -= zDampener * ySpan
            self.ymax += zDampener * ySpan
        else:
            # zoom in
            self.xmin += zDampener * xSpan
            self.xmax -= zDampener * xSpan
            self.ymin += zDampener * ySpan
            self.ymax -= zDampener * ySpan
        if (self.xmin > -1.0) or (self.ymin > -1.0):
            # zoom out
            self.xmin -= zDampener * xSpan
            self.xmax += zDampener * xSpan
            self.ymin -= zDampener * ySpan
            self.ymax += zDampener * ySpan
            self.fitView(self.xmin, self.xmax, self.ymin, self.ymax)
        else:
            self.fitView(self.xmin, self.xmax, self.ymin, self.ymax)


class Renderer:
    """
    Manages state of graphics objects. This is responsible for drawing on viewport.
    """

    def __init__(self, w, h):
        self.w = w
        self.h = h
        self.objects = set()

    def register(self, obj: GraphicsObject):
        self.objects.add(obj)
        obj.rendererId = self.__hash__()

    def deRegister(self, obj: GraphicsObject):
        self.objects.discard(obj)
        obj.rendererId = None

    def draw(self, gl, camera):
        """
        Call draw calls of all registered objects.
        Returns: None

        """
        gl.glEnable(gl.GL_DEPTH_TEST)
        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
        for obj in self.objects:
            if obj.rendererId is None:
                continue
            else:
                obj.setViewMat(camera.getViewMatrix())
                obj.setProjMat(camera.getProjMatrix())
                obj.drawCall(gl)


class GLViewport(QtWidgets.QOpenGLWidget):
    def __init__(self, parent=None):
        """
        Re-implements QOpenGLWidget.
        Args:
            parent: Optional.
        """
        super().__init__(parent)
        self.camera = Camera(QtGui.QVector3D(0.0, 0.0, 100.0), QtGui.QVector3D(0.0, 0.0, -1.0),
                             QtGui.QVector3D(0.0, 1.0, 0.0))
        self.renderer = Renderer(self.width(), self.height())
        self.axGrid = GraphicsObject("axGrid")
        self.m_gl = None
        self.m_time = 0
        self.m_deltatime = 0
        self.m_frameCount = 0
        self.numScheduledScalings = 0

    def initializeGL(self):
        defaultFmt = QtGui.QSurfaceFormat.defaultFormat()
        verProf = QtGui.QOpenGLVersionProfile(defaultFmt)
        self.m_gl = self.context().versionFunctions(verProf)
        self.m_gl.initializeOpenGLFunctions()
        self.m_gl.glClearColor(0.224, 0.224, 0.224, 1.0)

        # Co-ordinate lines.
        vertices = []
        indices = []
        delta = 1.0
        xmin = -5.0
        xmax = 5.0
        dx = delta
        ymin = -5.0
        ymax = 5.0
        dy = delta
        nHlines = int((xmax - xmin) / dx) + 1
        nVlines = int((ymax - ymin) / dy) + 1
        r = 0.4
        g = 0.4
        b = 0.4
        a = 0.302
        # Horizontal lines
        for i in range(0, nHlines):
            y = ymin + i * dy
            p1 = [xmin, y, 1.0]
            p2 = [xmax, y, 1.0]
            vertices.append(p1[0])
            vertices.append(p1[1])
            vertices.append(p1[2])
            vertices.append(r)
            vertices.append(g)
            vertices.append(b)
            vertices.append(a)
            vertices.append(p2[0])
            vertices.append(p2[1])
            vertices.append(p2[2])
            vertices.append(r)
            vertices.append(g)
            vertices.append(b)
            vertices.append(a)
        # Vertical lines
        for i in range(0, nVlines):
            x = xmin + i * dx
            p1 = [x, ymin, 1.0]
            p2 = [x, ymax, 1.0]
            vertices.append(p1[0])
            vertices.append(p1[1])
            vertices.append(p1[2])
            vertices.append(r)
            vertices.append(g)
            vertices.append(b)
            vertices.append(a)
            vertices.append(p2[0])
            vertices.append(p2[1])
            vertices.append(p2[2])
            vertices.append(r)
            vertices.append(g)
            vertices.append(b)
            vertices.append(a)
        for i in range(2 * nHlines + 2 * nVlines):
            indices.append(i)
        self.axGrid.createObjects()
        self.axGrid.buildShader()
        self.axGrid.bindAll()
        self.axGrid.setCount(len(vertices) / 3)
        self.axGrid.setDatatype(self.m_gl.GL_FLOAT)
        self.axGrid.setUsagePattern(QtGui.QOpenGLBuffer.StaticDraw)
        self.axGrid.allocateVertices(vertices)
        self.axGrid.allocateIndices(indices)
        self.axGrid.unbindAll()
        self.axGrid.setPrimitives(self.m_gl.GL_LINES)
        self.axGrid.setBlend(True)

        # Register graphics objects.
        self.renderer.register(self.axGrid)

        self.getGLinfo()

    def getGLinfo(self):
        profile = "None"
        if self.context().format().profile() == QtGui.QSurfaceFormat.CoreProfile:
            profile = "Core"
        elif self.context().format().profile() == QtGui.QSurfaceFormat.CompatibilityProfile:
            profile = "Compat"
        print("""
        OpenGL : {0}
        OpenGLES : {1}
        Version : {2}
        Major Version : {3}
        Minor Version : {4}
        Profile : {5}
        versionFunctions : {6}
            """.format(not self.context().isOpenGLES(), self.context().isOpenGLES(),
                       self.m_gl.glGetString(self.m_gl.GL_VERSION),
                       self.m_gl.glGetIntegerv(self.m_gl.GL_MAJOR_VERSION),
                       self.m_gl.glGetIntegerv(self.m_gl.GL_MINOR_VERSION),
                       profile, self.m_gl))

    def resizeGL(self, w: int, h: int):
        self.m_gl.glViewport(0, 0, w, h)
        # Re-adjust ortho
        self.camera.processResize(float(w), float(h), keepAspectRatio=True)
        self.update()

    def paintGL(self):
        if self.m_frameCount == 0:
            self.m_time = time.time()
        self.m_gl.glViewport(0, 0, self.width(), self.height())
        self.renderer.draw(self.m_gl, self.camera)
        self.m_deltatime = time.time() - self.m_time
        try:
            self.fps = 1. / (self.m_deltatime)
            # print(self.fps)
        except ZeroDivisionError:
            pass
        self.m_frameCount += 1
        self.m_time = time.time()

    def mousePressEvent(self, event: QtGui.QMouseEvent):
        """
        Allow pan with MMB, selection with LMB, context menu with RMB
        Args:
            event: the event.

        Returns: None

        """
        button = event.button()
        modifier = event.modifiers()
        posViewport = event.pos()
        posNdc = QtGui.QVector3D()
        posNdc.setX((2. / self.width()) * (posViewport.x() - (self.width() / 2.)))
        posNdc.setY((2. / self.height()) * (-posViewport.y() + (self.height() / 2.)))  # note the flip in sign.
        posNdc.setZ(0.0)
        if button == QtCore.Qt.MiddleButton:
            self.camera.processMousePan(posNdc, event.type())

        self.update()
        return super().mousePressEvent(event)

    def mouseMoveEvent(self, event: QtGui.QMouseEvent):
        """
        Allow pan with MMB, selection with LMB, context menu with RMB
        Args:
            event: the event.

        Returns: None

        """
        buttons = event.buttons()
        modifier = event.modifiers()
        posViewport = event.pos()
        posNdc = QtGui.QVector3D()
        posNdc.setX((2. / self.width()) * (posViewport.x() - (self.width() / 2.)))
        posNdc.setY((2. / self.height()) * (-posViewport.y() + (self.height() / 2.)))  # note the flip in sign.
        posNdc.setZ(0.0)
        if buttons == QtCore.Qt.MiddleButton:
            self.camera.processMousePan(posNdc, event.type())

        self.update()

        return super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event: QtGui.QMouseEvent):
        """
        Allow pan with MMB, selection with LMB, context menu with RMB
        Args:
            event: the event.

        Returns: None

        """
        button = event.button()
        modifier = event.modifiers()
        posViewport = event.pos()
        posNdc = QtGui.QVector3D()
        posNdc.setX((2. / self.width()) * (posViewport.x() - (self.width() / 2.)))
        posNdc.setY((2. / self.height()) * (-posViewport.y() + (self.height() / 2.)))  # note the flip in sign.
        posNdc.setZ(0.0)
        if button == QtCore.Qt.MiddleButton:
            self.camera.processMousePan(posNdc, event.type())

        self.update()

        return super().mouseReleaseEvent(event)

    def wheelEvent(self, event: QtGui.QWheelEvent):
        """
        Re-implement QGraphicsView's wheelEvent handler for smooooooth zoom.
        [https://wiki.qt.io/Smooth_Zoom_In_QGraphicsView]
        Args:
            event (QtGui.QWheelEvent): the event

        Returns:

        """

        numDegrees = event.angleDelta().y() / 8
        numSteps = numDegrees / 15
        self.numScheduledScalings += numSteps
        if self.numScheduledScalings * numSteps < 0:
            # Reset previously scheduled scalings if user changed wheel direction.
            self.numScheduledScalings = numSteps

        anim = QtCore.QTimeLine(350, self)
        anim.setUpdateInterval(8)

        anim.valueChanged.connect(partial(self.scalingTime))
        anim.finished.connect(partial(self.animFinished))
        anim.start()

    @QtCore.pyqtSlot()
    def scalingTime(self):
        factor = 1.0 + self.numScheduledScalings / 300.
        self.camera.processWheel(factor, float(self.width()), float(self.height()), self.m_deltatime)
        self.update()

    @QtCore.pyqtSlot()
    def animFinished(self):
        if self.numScheduledScalings > 0:
            self.numScheduledScalings -= 1
        else:
            self.numScheduledScalings += 1

    def keyPressEvent(self, event: QtGui.QKeyEvent):
        key = event.key()
        self.camera.processKbd(key, self.m_deltatime)
        self.update()

    def flush(self):
        self.axGrid.destroy()


if __name__ == '__main__':
    import sys

    QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_UseDesktopOpenGL)
    QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
    fmt = QtGui.QSurfaceFormat.defaultFormat()
    fmt.setVersion(2, 0)  # Request 2.0, that's the minimum we can get if PyQt5 was installed with desktop OpenGL
    fmt.setSamples(4)
    fmt.setSwapInterval(1)
    QtGui.QSurfaceFormat.setDefaultFormat(fmt)

    app = QtWidgets.QApplication(sys.argv)

    window = Window(app)
    window.resize(1000, 1000)
    window.show()

    sys.exit(app.exec_())

#graphshader.glsl
#shader vertex
#version 130

in vec3 position;
uniform mat4 u_mvp;
in vec4 color;
out vec4 vertexColor;

void main() {
    gl_Position = vec4(position.xyz, 1.0f);
    vertexColor = color;
}

#shader fragment
#version 130
in vec4 vertexColor;

void main(){
    gl_FragColor = vertexColor;
}

Upvotes: 0

Views: 937

Answers (1)

Jaswant P
Jaswant P

Reputation: 83

Alright, it appears to be resolved. Apparently, setAttributeBuffer usage is correct, there is nothing wrong with that! The error was in the shader. I did not multiply the model-view-projection matrix!

gl_Position = u_mvp * vec4(position.xyz, 1.0f); fixed it.

Upvotes: 1

Related Questions